/**
* This module provides MS VC runtime helper function that
* wrap differences between different versions of the MS C runtime
*
* Copyright: Copyright Digital Mars 2015.
* License: Distributed under the
*      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
*    (See accompanying file LICENSE)
* Source:    $(DRUNTIMESRC rt/_msvc.c)
* Authors:   Rainer Schuetze
*/

struct _iobuf
{
    char* _ptr;
    int   _cnt;  // _cnt and _base exchanged for VS2015
    char* _base;
    int   _flag;
    int   _file;
    int   _charbuf;
    int   _bufsiz;
    char* _tmpfname;
    // additional members in VS2015
};

typedef struct _iobuf FILE;
extern FILE* stdin;
extern FILE* stdout;
extern FILE* stderr;

FILE* __acrt_iob_func(int hnd);     // VS2015+
FILE* __iob_func();                 // VS2013-

int _set_output_format(int format); // VS2013-

int _vsnprintf(char* buffer, int buffer_length, const char* format, void* va_list); // VS2015-

//extern const char* __acrt_iob_func;
extern const char* _nullfunc = 0;

unsigned char msvcUsesUCRT;

#if defined _M_IX86
    #define C_PREFIX "_"
    typedef long size_t;
#elif defined _M_X64 || defined _M_ARM || defined _M_ARM64
    #define C_PREFIX ""
    typedef long long size_t;
#else
    #error Unsupported architecture
#endif

#define DECLARE_ALTERNATE_NAME(name, alternate_name)  \
    __pragma(comment(linker, "/alternatename:" C_PREFIX #name "=" C_PREFIX #alternate_name))

DECLARE_ALTERNATE_NAME (__acrt_iob_func, _nullfunc);
DECLARE_ALTERNATE_NAME (__iob_func, _nullfunc);
DECLARE_ALTERNATE_NAME (_set_output_format, _nullfunc);

void init_msvc()
{
    if (&__acrt_iob_func != (void*) &_nullfunc)
    {
        stdin = __acrt_iob_func(0);
        stdout = __acrt_iob_func(1);
        stderr = __acrt_iob_func(2);
        msvcUsesUCRT = 1;
    }
    else if (&__iob_func != (void*) &_nullfunc)
    {
        FILE* fp = __iob_func();
        stdin = fp;
        stdout = fp + 1;
        stderr = fp + 2;
    }
    if (&_set_output_format != (void*) &_nullfunc)
    {
        const int _TWO_DIGIT_EXPONENT = 1;
        _set_output_format(_TWO_DIGIT_EXPONENT);
    }
}

// VS2017+ wraps (v)snprintf into an inlined function calling __stdio_common_vsprintf
//  wrap it back to the original function if it doesn't exist in the C library
int _msvc_stdio_common_vsprintf(
    unsigned __int64 const options,
    char* const buffer,
    size_t const buffer_count,
    char const* const format,
    void* const locale,
    void* const arglist
)
{
    static const int _CRT_INTERNAL_PRINTF_STANDARD_SNPRINTF_BEHAVIOR = 2;
    int r = _vsnprintf(buffer, buffer_count, format, arglist);
    if ((options & _CRT_INTERNAL_PRINTF_STANDARD_SNPRINTF_BEHAVIOR) &&
        (buffer_count != 0 && buffer))
    {
        // mimic vsnprintf semantics for most use cases
        if (r == buffer_count)
        {
            buffer[buffer_count - 1] = 0;
            return buffer_count;
        }
        if (r == -1)
        {
            buffer[buffer_count - 1] = 0;
            return _vsnprintf(0, 0, format, arglist);
        }
    }
    return r;
}

DECLARE_ALTERNATE_NAME(__stdio_common_vsprintf, _msvc_stdio_common_vsprintf);

// VS2015+ provides C99-conformant (v)snprintf functions, so weakly
// link to legacy _(v)snprintf (not C99-conformant!) for VS2013- only

DECLARE_ALTERNATE_NAME (snprintf, _snprintf);
DECLARE_ALTERNATE_NAME (vsnprintf, _vsnprintf);

// VS2013- implements these functions as macros, VS2015+ provides symbols

DECLARE_ALTERNATE_NAME (_fputc_nolock, _msvc_fputc_nolock);
DECLARE_ALTERNATE_NAME (_fgetc_nolock, _msvc_fgetc_nolock);
DECLARE_ALTERNATE_NAME (rewind, _msvc_rewind);
DECLARE_ALTERNATE_NAME (clearerr, _msvc_clearerr);
DECLARE_ALTERNATE_NAME (feof, _msvc_feof);
DECLARE_ALTERNATE_NAME (ferror, _msvc_ferror);
DECLARE_ALTERNATE_NAME (fileno, _msvc_fileno);

// VS2013- helper functions
int _filbuf(FILE* fp);
int _flsbuf(int c, FILE* fp);

DECLARE_ALTERNATE_NAME (_filbuf, _nullfunc);
DECLARE_ALTERNATE_NAME (_flsbuf, _nullfunc);

int _msvc_fputc_nolock(int c, FILE* fp)
{
    fp->_cnt = fp->_cnt - 1;
    if (fp->_cnt >= 0)
    {
        *(fp->_ptr) = (char)c;
        fp->_ptr = fp->_ptr + 1;
        return (char)c;
    }
    else
        return _flsbuf(c, fp);
}

int _msvc_fgetc_nolock(FILE* fp)
{
    fp->_cnt = fp->_cnt - 1;
    if (fp->_cnt >= 0)
    {
        char c = *(fp->_ptr);
        fp->_ptr = fp->_ptr + 1;
        return c;
    }
    else
        return _filbuf(fp);
}

enum
{
    SEEK_SET = 0,
    _IOEOF   = 0x10,
    _IOERR   = 0x20
};

int fseek(FILE* fp, long off, int whence);

void _msvc_rewind(FILE* stream)
{
    fseek(stream, 0L, SEEK_SET);
    stream->_flag = stream->_flag & ~_IOERR;
}

void _msvc_clearerr(FILE* stream)
{
    stream->_flag = stream->_flag & ~(_IOERR | _IOEOF);
}

int  _msvc_feof(FILE* stream)
{
    return stream->_flag & _IOEOF;
}

int  _msvc_ferror(FILE* stream)
{
    return stream->_flag & _IOERR;
}

int  _msvc_fileno(FILE* stream)
{
    return stream->_file;
}



/**
 * 32-bit x86 MS VC runtimes lack most single-precision math functions.
 * Declare alternate implementations to be pulled in from msvc_math.c.
 */
#if defined _M_IX86

DECLARE_ALTERNATE_NAME (acosf,  _msvc_acosf);
DECLARE_ALTERNATE_NAME (asinf,  _msvc_asinf);
DECLARE_ALTERNATE_NAME (atanf,  _msvc_atanf);
DECLARE_ALTERNATE_NAME (atan2f, _msvc_atan2f);
DECLARE_ALTERNATE_NAME (cosf,   _msvc_cosf);
DECLARE_ALTERNATE_NAME (sinf,   _msvc_sinf);
DECLARE_ALTERNATE_NAME (tanf,   _msvc_tanf);
DECLARE_ALTERNATE_NAME (coshf,  _msvc_coshf);
DECLARE_ALTERNATE_NAME (sinhf,  _msvc_sinhf);
DECLARE_ALTERNATE_NAME (tanhf,  _msvc_tanhf);
DECLARE_ALTERNATE_NAME (expf,   _msvc_expf);
DECLARE_ALTERNATE_NAME (logf,   _msvc_logf);
DECLARE_ALTERNATE_NAME (log10f, _msvc_log10f);
DECLARE_ALTERNATE_NAME (powf,   _msvc_powf);
DECLARE_ALTERNATE_NAME (sqrtf,  _msvc_sqrtf);
DECLARE_ALTERNATE_NAME (ceilf,  _msvc_ceilf);
DECLARE_ALTERNATE_NAME (floorf, _msvc_floorf);
DECLARE_ALTERNATE_NAME (fmodf,  _msvc_fmodf);
DECLARE_ALTERNATE_NAME (modff,  _msvc_modff);

#endif // _M_IX86
