//  ===========================================================================================
//  The file stream class implements one-to-one binary byte stream I/O to an underlying file.
//  On Windows this means text mode line endings (CR LF) must be handled on top of this stream,
//  for example using WideningStream/NarrowingStream layers. Actual I/O is delegated to external
//  functions implemented by the Cminor virtual machine.
//  ============================================================================================

using System;

namespace System.IO
{
    public const int stdin = 0;
    public const int stdout = 1;
    public const int stderr = 2;

    public enum FileMode : byte
    {
        append, create, open
    }

    public enum FileAccess : byte
    {
        read, readWrite, write
    }

    public enum Origin : byte
    {
        seekSet, seekCur, seekEnd
    }

    public class FileStream : Stream, Closable
    {
        public FileStream(int fileHandle, FileAccess access)
        {
            this.mode = DefaultMode(access);
            this.access = access;
            this.fileHandle = fileHandle;
        }
        public FileStream(String filePath, FileMode mode) : this(filePath, mode, DefaultAccess(mode))
        {
        }
        public FileStream(String filePath, FileMode mode, FileAccess access)
        {
            if (filePath == null)
            {
                throw new ArgumentNullException("provided file path is null");
            }
            this.filePath = filePath;
            this.mode = mode;
            this.access = access;
            this.fileHandle = OpenFile(filePath, mode, access);
        }
        public bool IsOpen() 
        {
            return fileHandle != -1;
        }
        public override void Close()
        {
            if (fileHandle != -1 && fileHandle != stdin && fileHandle != stdout && fileHandle != stderr)
            {
                CloseFile(fileHandle);
                fileHandle = -1;
            }
        }
        public override void WriteByte(byte value)
        {
            WriteByteToFile(fileHandle, value);
        }
        public override void Write(byte[] buffer, int count)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("provided buffer is null");
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException("invalid count");
            }
            WriteFile(fileHandle, buffer, count);
        }
        public override int ReadByte()
        {
            return ReadByteFromFile(fileHandle);
        }
        public override int Read(byte[] buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("provided buffer is null");
            }
            return ReadFile(fileHandle, buffer);
        }
        public override void Seek(int pos, Origin origin)
        {
            SeekFile(fileHandle, pos, origin);
        }
        public override int Tell()
        {
            return TellFile(fileHandle);
        }
        private FileAccess DefaultAccess(FileMode mode)
        {
            if (mode == FileMode.append || mode == FileMode.create) return FileAccess.write;
            return FileAccess.read;
        }
        private FileMode DefaultMode(FileAccess access)
        {
            if (access == FileAccess.read || access == FileAccess.readWrite) return FileMode.open;
            return FileMode.create;
        }
        private String filePath;
        private FileMode mode;
        private FileAccess access;
        private int fileHandle;
    }

    [vmf=fopen]
    internal extern int OpenFile(string filePath, FileMode mode, FileAccess access);

    [vmf=fclose]
    internal extern void CloseFile(int fileHandle);

    [vmf=fputb]
    internal extern void WriteByteToFile(int fileHandle, byte value);

    [vmf=fwrite]
    internal extern void WriteFile(int fileHandle, byte[] buffer, int count);

    [vmf=fgetb]
    internal extern int ReadByteFromFile(int fileHandle);

    [vmf=fread]
    internal extern int ReadFile(int fileHandle, byte[] buffer);

    [vmf=fseek]
    internal extern void SeekFile(int fileHandle, int pos, Origin origin);

    [vmf=ftell]
    internal extern int TellFile(int fileHandle);
}