
// Written in the D programming language.

/* Written by Walter Bright and Andrei Alexandrescu
 * www.digitalmars.com
 * Placed in the Public Domain.
 */

/********************************
 * Standard I/O functions that extend $(B std.c.stdio).
 * $(B std.c.stdio) is automatically imported when importing
 * $(B std.stdio).
 * Macros:
 *	WIKI=Phobos/StdStdio
 */

module std.stdio;

public import std.c.stdio;

import std.format;
import std.utf;
import std.string;
import std.gc;
import std.c.stdlib;
import std.c.string;
import std.c.stddef;
import std.conv;


version (DigitalMars)
{
    version (Windows)
    {
	// Specific to the way Digital Mars C does stdio
	version = DIGITAL_MARS_STDIO;
    }
}

version (DIGITAL_MARS_STDIO)
{
}
else
{
    // Specific to the way Gnu C does stdio
    version = GCC_IO;
    import std.c.linux.linux;
}

version (DIGITAL_MARS_STDIO)
{
    extern (C)
    {
	/* **
	 * Digital Mars under-the-hood C I/O functions
	 */
	int _fputc_nlock(int, FILE*);
	int _fputwc_nlock(int, FILE*);
	int _fgetc_nlock(FILE*);
	int _fgetwc_nlock(FILE*);
	int __fp_lock(FILE*);
	void __fp_unlock(FILE*);
    }
    alias _fputc_nlock FPUTC;
    alias _fputwc_nlock FPUTWC;
    alias _fgetc_nlock FGETC;
    alias _fgetwc_nlock FGETWC;

    alias __fp_lock FLOCK;
    alias __fp_unlock FUNLOCK;
}
else version (GCC_IO)
     {
         /* **
          * Gnu under-the-hood C I/O functions; see
          * http://www.gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html#I_002fO-on-Streams
          */
         extern (C)
         {
             int fputc_unlocked(int, FILE*);
             int fputwc_unlocked(wchar_t, FILE*);
             int fgetc_unlocked(FILE*);
             int fgetwc_unlocked(FILE*);
             void flockfile(FILE*);
             void funlockfile(FILE*);
             ssize_t getline(char**, size_t*, FILE*);
             ssize_t getdelim (char**, size_t*, int, FILE*);
         }

         alias fputc_unlocked FPUTC;
         alias fputwc_unlocked FPUTWC;
         alias fgetc_unlocked FGETC;
         alias fgetwc_unlocked FGETWC;

         alias flockfile FLOCK;
         alias funlockfile FUNLOCK;
     }
else
{
    static assert(0, "unsupported C I/O system");
}


/*********************
 * Thrown if I/O errors happen.
 */
class StdioException : Exception
{
    uint errno;			// operating system error code

    this(const char[] msg)
    {
	super(msg);
    }

    this(uint errno)
    {	const(char*) s = strerror(errno);
	super(std.string.toString(s).dup);
    }

    static void opCall(const char[] msg)
    {
	throw new StdioException(msg);
    }

    static void opCall()
    {
	throw new StdioException(getErrno());
    }
}

private
void writefx(FILE* fp, TypeInfo[] arguments, void* argptr, int newline=false)
{   int orientation;

    orientation = fwide(fp, 0);

    /* Do the file stream locking at the outermost level
     * rather than character by character.
     */
    FLOCK(fp);
    scope(exit) FUNLOCK(fp);

    if (orientation <= 0)		// byte orientation or no orientation
    {
	void putc(dchar c)
	{
	    if (c <= 0x7F)
	    {
		FPUTC(c, fp);
	    }
	    else
	    {   char[4] buf;
		string b;

		b = std.utf.toUTF8(buf, c);
		for (size_t i = 0; i < b.length; i++)
		    FPUTC(b[i], fp);
	    }
	}

	std.format.doFormat(&putc, arguments, argptr);
	if (newline)
	    FPUTC('\n', fp);
    }
    else if (orientation > 0)		// wide orientation
    {
	version (Windows)
	{
	    void putcw(dchar c)
	    {
		assert(isValidDchar(c));
		if (c <= 0xFFFF)
		{
		    FPUTWC(c, fp);
		}
		else
		{   wchar[2] buf;

		    buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
		    buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00);
		    FPUTWC(buf[0], fp);
		    FPUTWC(buf[1], fp);
		}
	    }
	}
	else version (linux)
             {
                 void putcw(dchar c)
                 {
                     FPUTWC(c, fp);
                 }
             }
	else
	{
	    static assert(0);
	}

	std.format.doFormat(&putcw, arguments, argptr);
	if (newline)
	    FPUTWC('\n', fp);
    }
}


/***********************************
 * Arguments are formatted per the
 * $(LINK2 std_format.html#format-string, format strings)
 * and written to $(B stdout).
 */

void writef(...)
{
    writefx(stdout, _arguments, _argptr, 0);
}

/***********************************
 * Same as $(B writef), but a newline is appended
 * to the output.
 */

void writefln(...)
{
    writefx(stdout, _arguments, _argptr, 1);
}

/***********************************
 * Same as $(B writef), but output is sent to the
 * stream fp instead of $(B stdout).
 */

void fwritef(FILE* fp, ...)
{
    writefx(fp, _arguments, _argptr, 0);
}

/***********************************
 * Same as $(B writefln), but output is sent to the
 * stream fp instead of $(B stdout).
 */

void fwritefln(FILE* fp, ...)
{
    writefx(fp, _arguments, _argptr, 1);
}

/**********************************
 * Read line from stream fp.
 * Returns:
 *	null for end of file,
 *	char[] for line read from fp, including terminating '\n'
 * Params:
 *	fp = input stream
 * Throws:
 *	$(B StdioException) on error
 * Example:
 *	Reads $(B stdin) and writes it to $(B stdout).
---
import std.stdio;

int main()
{
    char[] buf;
    while ((buf = readln()) != null)
	writef("%s", buf);
    return 0;
}
---
*/
char[] readln(FILE* fp = stdin)
{
    char[] buf;
    readln(fp, buf);
    return buf;
}

/**********************************
 * Read line from stream fp and write it to buf[],
 * including terminating '\n'.
 *
 * This is often faster than readln(FILE*) because the buffer
 * is reused each call. Note that reusing the buffer means that
 * the previous contents of it need to be copied if needed.
 * Params:
 *	fp = input stream
 *	buf = buffer used to store the resulting line data. buf
 *		is resized as necessary.
 * Returns:
 *	0 for end of file, otherwise
 *	number of characters read
 * Throws:
 *	$(B StdioException) on error
 * Example:
 *	Reads $(B stdin) and writes it to $(B stdout).
---
import std.stdio;

int main()
{
    char[] buf;
    while (readln(stdin, buf))
	writef("%s", buf);
    return 0;
}
---
*/
size_t readln(FILE* fp, inout char[] buf)
{
    version (DIGITAL_MARS_STDIO)
    {
	FLOCK(fp);
	scope(exit) FUNLOCK(fp);

	if (__fhnd_info[fp._file] & FHND_WCHAR)
	{   /* Stream is in wide characters.
	     * Read them and convert to chars.
	     */
	    static assert(wchar_t.sizeof == 2);
	    buf.length = 0;
	    int c2;
	    for (int c; (c = FGETWC(fp)) != -1; )
	    {
		if ((c & ~0x7F) == 0)
		{   buf ~= c;
		    if (c == '\n')
			break;
		}
		else
		{
		    if (c >= 0xD800 && c <= 0xDBFF)
		    {
			if ((c2 = FGETWC(fp)) != -1 ||
			    c2 < 0xDC00 && c2 > 0xDFFF)
			{
			    StdioException("unpaired UTF-16 surrogate");
			}
			c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
		    }
		    std.utf.encode(buf, c);
		}
	    }
	    if (ferror(fp))
		StdioException();
	    return buf.length;
	}

	auto sz = std.gc.capacity(buf.ptr);
	//auto sz = buf.length;
	buf = buf.ptr[0 .. sz];
	if (fp._flag & _IONBF)
	{
	    /* Use this for unbuffered I/O, when running
	     * across buffer boundaries, or for any but the common
	     * cases.
	     */
        L1:
	    char *p;

	    if (sz)
	    {
		p = buf.ptr;
	    }
	    else
	    {
		sz = 64;
		p = cast(char*) std.gc.malloc(sz);
		std.gc.hasNoPointers(p);
		buf = p[0 .. sz];
	    }
	    size_t i = 0;
	    for (int c; (c = FGETC(fp)) != -1; )
	    {
		if ((p[i] = c) != '\n')
		{
		    i++;
		    if (i < sz)
			continue;
		    buf = p[0 .. i] ~ readln(fp);
		    return buf.length;
		}
		else
		{
		    buf = p[0 .. i + 1];
		    return i + 1;
		}
	    }
	    if (ferror(fp))
		StdioException();
	    buf = p[0 .. i];
	    return i;
	}
	else
	{
	    int u = fp._cnt;
	    char* p = fp._ptr;
	    int i;
	    if (fp._flag & _IOTRAN)
	    {   /* Translated mode ignores \r and treats ^Z as end-of-file
		 */
		char c;
		while (1)
		{
		    if (i == u)		// if end of buffer
			goto L1;	// give up
		    c = p[i];
		    i++;
		    if (c != '\r')
		    {
			if (c == '\n')
			    break;
			if (c != 0x1A)
			    continue;
			goto L1;
		    }
		    else
		    {   if (i != u && p[i] == '\n')
			    break;
			goto L1;
		    }
		}
		if (i > sz)
		{
		    buf = cast(char[])std.gc.malloc(i);
		    std.gc.hasNoPointers(buf.ptr);
		}
		if (i - 1)
		    memcpy(buf.ptr, p, i - 1);
		buf[i - 1] = '\n';
		if (c == '\r')
		    i++;
	    }
	    else
	    {
		while (1)
		{
		    if (i == u)		// if end of buffer
			goto L1;	// give up
		    auto c = p[i];
		    i++;
		    if (c == '\n')
			break;
		}
		if (i > sz)
		{
		    buf = cast(char[])std.gc.malloc(i);
		    std.gc.hasNoPointers(buf.ptr);
		}
		memcpy(buf.ptr, p, i);
	    }
	    fp._cnt -= i;
	    fp._ptr += i;
	    buf = buf[0 .. i];
	    return i;
	}
    }
    else version (GCC_IO)
         {
             if (fwide(fp, 0) > 0)
             {   /* Stream is in wide characters.
                  * Read them and convert to chars.
                  */
                 FLOCK(fp);
                 scope(exit) FUNLOCK(fp);
                 version (Windows)
                 {
                     buf.length = 0;
                     int c2;
                     for (int c; (c = FGETWC(fp)) != -1; )
                     {
                         if ((c & ~0x7F) == 0)
                         {   buf ~= c;
                             if (c == '\n')
                                 break;
                         }
                         else
                         {
                             if (c >= 0xD800 && c <= 0xDBFF)
                             {
                                 if ((c2 = FGETWC(fp)) != -1 ||
                                     c2 < 0xDC00 && c2 > 0xDFFF)
                                 {
                                     StdioException("unpaired UTF-16 surrogate");
                                 }
                                 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
                             }
                             std.utf.encode(buf, c);
                         }
                     }
                     if (ferror(fp))
                         StdioException();
                     return buf.length;
                 }
                 else version (linux)
                      {
                          buf.length = 0;
                          for (int c; (c = FGETWC(fp)) != -1; )
                          {
                              if ((c & ~0x7F) == 0)
                                  buf ~= c;
                              else
                                  std.utf.encode(buf, cast(dchar)c);
                              if (c == '\n')
                                  break;
                          }
                          if (ferror(fp))
                              StdioException();
                          return buf.length;
                      }
                 else
                 {
                     static assert(0);
                 }
             }

             char *lineptr = null;
             size_t n = 0;
             auto s = getdelim(&lineptr, &n, '\n', fp);
             scope(exit) free(lineptr);
             if (s < 0)
             {
                 if (ferror(fp))
                     StdioException();
                 buf.length = 0;		// end of file
                 return 0;
             }
             buf = buf.ptr[0 .. std.gc.capacity(buf.ptr)];
             if (s <= buf.length)
             {
                 buf.length = s;
                 buf[] = lineptr[0 .. s];
             }
             else
             {
                 buf = lineptr[0 .. s].dup;
             }
             return s;
         }
    else
    {
	static assert(0);
    }
}

/** ditto */
size_t readln(inout char[] buf)
{
    return readln(stdin, buf);
}

// Added by Andrei
/////////////////////////////////////////////////////////////////////////////////
void writeln(T...)(T args)
{
    return write(args, '\n');
}

/////////////////////////////////////////////////////////////////////////////////
void write(T...)(T args) {
    static if (!is(typeof(args[0]) : FILE*)) {
        return write(stdout, args);
    } else {
        auto target = args[0];
        FLOCK(target);
        scope(exit) FUNLOCK(target);
        fwrite_unlocked(target, args[1 .. $]);
    }
}

/////////////////////////////////////////////////////////////////////////////////
private void fwrite_unlocked(T...)(FILE* f, T args) {
    alias typeof(args[0]) T0;
    static const bool isString = is(T0 : string) || is(T0 : wstring)
        || is(T0 : dstring);
    static if (is(T0 : T0[]) && !isString) {
        fwrite_unlocked(f, '[');
        final n = args[0].length;
        for (size_t i = 0; ; ) {
            fwrite_unlocked(f, args[0][i]);
            if (++i == n) break;
            fwrite_unlocked(f, ',');
        }
        fwrite_unlocked(f, ']');
    } else {
        static if (isString) {
            final s = args[0];
        } else {
            final s = to!(string)(args[0]);
        }
        if (std.c.stdio.fwrite(s.ptr, s[0].sizeof, s.length, f) != s.length) {
            StdioException();
        }
    }
    static if (args.length > 1) {
        fwrite_unlocked(f, args[1 .. $]);
    }
}

