#ifndef _FILE_IO_H_
#define _FILE_IO_H_

#include <stdio.h>
#if !defined(_WIN32_WCE)
#include <errno.h>
#endif
#include "./types.h"

const int END_OF_FILE       = -10001;
const int MAX_FILENAME_LEN  = 512;
const int MEM_BUF_SIZE = 8*1024*1024;

#if defined(WIN32)
    inline bool open_for_write(const char* file_name, HANDLE &handle)
    {
#if defined(_UNICODE)
		wchar_t filename[MAX_FILENAME_LEN];
		mbstowcs(filename, file_name, MAX_FILENAME_LEN);
#else
		const char* filename= file_name;
#endif
        handle = CreateFile(filename,
                            GENERIC_WRITE,
                            FILE_SHARE_READ,
                            NULL,
                            CREATE_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL );

        if (handle == INVALID_HANDLE_VALUE) {
            printf("BufferToFile() - unable to open file %s for write\n",file_name);
            printf("GetLastError = %d\n", GetLastError());
            return false;
        } else {
            return true;
        }
    }

    inline bool open_for_read(const char* file_name, HANDLE &handle) 
    {
#if defined(_UNICODE)
		wchar_t filename[MAX_FILENAME_LEN];
		mbstowcs(filename, file_name, MAX_FILENAME_LEN);
#else
		const char* filename= file_name;
#endif
	    handle = CreateFile( filename,
                                 GENERIC_READ,
                                 FILE_SHARE_WRITE,
                                 NULL,
                                 OPEN_EXISTING,
                                 0,
                                 NULL );

        if (handle == INVALID_HANDLE_VALUE) {
            printf("BufferToFile() - unable to open file %s for read\n",file_name);
            printf("GetLastError = %d\n", GetLastError());
            return false;
        } else {
            return true;
        }
    }

    /** Convenience object that handles buffer reads/writes to file */
    class FileBufferIO {
        char filename_m[255];
        char options_m[10];
        HANDLE m_hWriteFile;
        HANDLE m_hReadFile;
    public:
        FileBufferIO() 
        {
            m_hReadFile = INVALID_HANDLE_VALUE;
            m_hWriteFile= INVALID_HANDLE_VALUE;
        }

        FileBufferIO(const char* output_filename, int membuf_size = MEM_BUF_SIZE) {
            m_hReadFile = INVALID_HANDLE_VALUE;
            m_hWriteFile= INVALID_HANDLE_VALUE;

            strcpy(options_m, "wb");
            strcpy(filename_m, output_filename);

            open_for_write(output_filename, m_hWriteFile);
        }

        /** constructor */
        FileBufferIO(const char* output_filename, const char * options, int membuf_size = MEM_BUF_SIZE)
        {
            openFile(output_filename, options, membuf_size);
        }

        /** destructor */
        ~FileBufferIO() 
        {
            closeFile();
        }

        int IsFileOpened() { 
            if (!strcmp(options_m, "w")  || !strcmp(options_m, "w+") ||
                !strcmp(options_m, "wb") || !strcmp(options_m, "wb+"))
            {
                return (m_hWriteFile != INVALID_HANDLE_VALUE);
            } else {
                return (m_hReadFile != INVALID_HANDLE_VALUE);
            }
        }

        void openFile(const char *output_filename, const char *options, int membuf_size = MEM_BUF_SIZE) 
        {
            m_hReadFile = INVALID_HANDLE_VALUE;
            m_hWriteFile= INVALID_HANDLE_VALUE;

            strcpy(filename_m,output_filename);

            if (options == NULL) {
                strcpy(options_m, "wb");
            } else {
                strcpy(options_m, options);
            }

            if (!strcmp(options_m, "w")  || !strcmp(options_m, "w+") ||
                !strcmp(options_m, "wb") || !strcmp(options_m, "wb+"))
            {
                open_for_write(output_filename, m_hWriteFile);
            
            } else if 
               (!strcmp(options_m, "r")  || !strcmp(options_m, "r+") ||
                !strcmp(options_m, "rb") || !strcmp(options_m, "rb+"))
            {
                open_for_read(output_filename, m_hReadFile);
            }
        }

        void closeFile() 
        {
            if (m_hWriteFile != NULL &&
                m_hWriteFile != INVALID_HANDLE_VALUE) 
            {
                CloseHandle(m_hWriteFile);
                m_hWriteFile = INVALID_HANDLE_VALUE;
            }
            if (m_hReadFile != NULL &&
                m_hReadFile != INVALID_HANDLE_VALUE) 
            {
                CloseHandle(m_hReadFile);
                m_hReadFile = INVALID_HANDLE_VALUE;
            }
        }

        /** performs file writing processing (ex: write buffer to file)
         *  @Parameters
         *      p_read_buf = pointer to the buffer
         *      data_size = size of valid data in the buffer (<= buf_size)
         */
        bool writeFile(PVOID p_read_buf,UINT32 data_size) {
            int bWriteReturn = 0, retResult = 0;
            DWORD dwBytesWritten = 0, dwErr = 0;

            if (m_hWriteFile == INVALID_HANDLE_VALUE) 
            {
                printf("\nwriteFile() - null file pointer, aborting\n");
                return false;
            }
            // write to file
            bWriteReturn = WriteFile( m_hWriteFile,
                                      p_read_buf,
                                      data_size,
                                      &dwBytesWritten,
                                      NULL );

            if (!bWriteReturn) 
            {
                dwErr = GetLastError();
                printf( "\nwriteFile(): WriteFile failed.  GetLastError: %d.\n", dwErr );
                return false;
	        }

            return true;
        }

        /** performs file reading processing (ex: read from file)
         *  @Parameters
         *      p_read_buf = pointer to the buffer
         *      data_size  = size of valid data in the buffer (<= buf_size)
         *      read_size  = return result of the actual number of bytes read 
         */
        int readFile(PVOID p_read_buf, UINT32 data_size, UINT32 &read_size) {
            DWORD dwBytesRead = 0, dwErr = 0;

            int bReadReturn = 0, retResult = SUCCESS;

            if (m_hReadFile == INVALID_HANDLE_VALUE) 
            {
                printf("\nreadFile() - null file pointer, aborting\n");
                return FAILURE;
            }
            // read from file
            bReadReturn = ReadFile( m_hReadFile, p_read_buf, data_size, &dwBytesRead, NULL);

            if (!bReadReturn || dwBytesRead == 0) {
                dwErr = GetLastError();
                printf("\nreadFile: ReadFile failed.  GetLastError: %d.\n", dwErr );

                switch (dwErr) {
                  case ERROR_HANDLE_EOF: retResult = END_OF_FILE;   break;
                  case ERROR_IO_PENDING: retResult = FAILURE;       break;
                  default:               retResult = FAILURE;       break;
                }
                if (dwBytesRead == 0) {
                    retResult = END_OF_FILE;
                }
            }
            read_size = dwBytesRead;
            return retResult;
        }
    };

#else

    // linux file control routines
    /** open a new file for writing - nonblocking, truncating
        @return true iff successful
     */
    inline bool open_for_write(const char* file_name, HANDLE &handle) {
#ifdef _LARGEFILE64_SOURCE
        handle = open(file_name, O_WRONLY | O_CREAT | O_TRUNC | O_NDELAY | O_LARGEFILE );
#else
        handle = open(file_name, O_WRONLY | O_CREAT | O_TRUNC | O_NDELAY );
#endif

        // check for failure
        if (handle==-1) {
            printf("HANDLE = UNABLE TO GET VALID HANDLE, errno=%d\n", errno);
            return false;
        }
        return true;
    }

    /** write to a file */
    inline int write_to_file(const HANDLE handle, const void* p_buf,
        const unsigned int bytes_to_write ) {
        int wrote = write(handle, p_buf, bytes_to_write);

        if (wrote == -1) {
            printf("error writing to disk: ");
            if (errno == EBADF) {
                printf("EBADF\n");
            } else if (errno == EINVAL) {
                printf("EINVAL\n");
            } else if (errno == EFAULT) {
                printf("EFAULT\n");
            } else if (errno == EFBIG) {
                printf("EFBIG\n");
            } else if (errno == EPIPE) {
                printf("EPIPE\n");
            } else if (errno == EAGAIN) {
                printf("EAGAIN\n");
            } else if (errno == EINTR) {
                printf("EINTR\n");
            } else if (errno == ENOSPC) {
                printf("ENOSPC\n");
            } else if (errno == EIO) {
                printf("EIO\n");
            } else {
                printf("Unknown Error\n");
            }
        }
        return SUCCESS;
    }

    /** close file */
    inline int close_file(HANDLE fd) {
        fsync(fd);
        return close(fd);
    }

    /** Convenience object that handles buffer reads/writes to file */
    class FileBufferIO {
        char filename_m[255];
        char options_m[10];

        HANDLE fHandle_m;

        FILE *pFile_m;

        char *pMemBuf_m;
        unsigned int memBufSize_m;
        unsigned int accDataSize_m;


    public:
        /** constructor without opening parameters */
        FileBufferIO() {
            fHandle_m   = 0;
            pFile_m     = NULL;
            pMemBuf_m   = NULL;
        }

        /** constructor that open's the file and allocates membuf_size for buffered File I/O */
        FileBufferIO(const char* output_filename, int membuf_size = MEM_BUF_SIZE) {
            strcpy(filename_m,output_filename);

            // open / create file
            openFile(output_filename, "wb", membuf_size);
        }

        /** constructor that open's the file wiht options and allocates membuf_size for buffered File I/O */
        FileBufferIO(const char* output_filename, const char *options, int membuf_size = MEM_BUF_SIZE) {
            openFile(output_filename, options, membuf_size);
        }


        /** destructor */
        ~FileBufferIO() {
            closeFile();
        }

        /** Check to see if file has been opened */
        int IsFileOpened() { 
            if (!strcmp(options_m, "w")  || !strcmp(options_m, "w+") ||
                !strcmp(options_m, "wb") || !strcmp(options_m, "wb+"))
            {
                return (fHandle_m != -1);
            } else {
                return (pFile_m != NULL); 
            }
        }

        /** openFile for file read/write and allocated membuf_size
         *  @Parameters
         *      output_filename = pointer to the buffer
		 *      options         = actual read/write flag options for write
         *      membuf_size     = size of the memory buffer to allocate
		 */
        void openFile(const char * output_filename, const char *options, int membuf_size = MEM_BUF_SIZE) 
        {
            strcpy(filename_m,output_filename);

            // writing is different than reading
            if (!strcmp(options, "w")  || !strcmp(options, "w+") ||
                !strcmp(options, "wb") || !strcmp(options, "wb+") ||
                (options == NULL ))
            {
                open_for_write( filename_m, fHandle_m );
                pFile_m = NULL;
                strcpy(options_m, "wb");
            } else {
                fHandle_m = -1;
                pFile_m = fopen(filename_m, options);
                strcpy(options_m, options);

                if (pFile_m == NULL) {
                    printf("BufferToFile() - cannot open file %s\n",filename_m);
                }
            }

            // allocate memory buffer
            memBufSize_m = membuf_size;
            accDataSize_m = 0;
            pMemBuf_m = (char*) malloc(memBufSize_m);
        }

        /** closeFile and free all memory allocated resources */
        void closeFile()
        {
            // flush mem buf if necessary
            if (pMemBuf_m!=NULL) {
                if ( accDataSize_m!=0 && fHandle_m!=-1 ) {
                    write_to_file( fHandle_m, pMemBuf_m, accDataSize_m );
                }
                free(pMemBuf_m);
                pMemBuf_m = NULL;
            }

            // close file
            if (fHandle_m != -1) {
                close_file(fHandle_m);
                fHandle_m = -1;
            }

            if (pFile_m!=NULL) {
                fclose(pFile_m);
                pFile_m = NULL;
            }
        }

        /** performs file writing processing (ex: write buffer to file)
         *  @Parameters
         *      p_read_buf = pointer to the buffer
         *      data_size  = size of valid data in the buffer (<= buf_size)
         */
        bool writeFile(PVOID p_read_buf,UINT32 data_size) {
            if (fHandle_m==-1) {
                printf("writeFile() - invalid handle, aborting\n");
                return false;
            }

            if ( memBufSize_m > (accDataSize_m + data_size) ) {
                // copy to memory buffer if possible
                memcpy(pMemBuf_m + accDataSize_m, p_read_buf, data_size);
                accDataSize_m += data_size;
            } else if (data_size >= memBufSize_m) {
                // write memory buffer to file, then write data to file
                write_to_file( fHandle_m, pMemBuf_m, accDataSize_m);
                write_to_file( fHandle_m, p_read_buf, data_size);
                accDataSize_m = 0;
            } else {
                // write memory buffer to file
                write_to_file( fHandle_m, pMemBuf_m, accDataSize_m);
                memcpy(pMemBuf_m, p_read_buf, data_size);
                accDataSize_m = data_size;
            }

            // write to file
            // char *p_byte = (char*)p_read_buf;
            // fwrite(p_read_buf,sizeof(char),data_size,pFile_m);
            return true;
        }

        /** performs file reading processing (ex: read from file)
         *  @Parameters
         *      p_read_buf = pointer to the buffer
         *      data_size  = size of valid data in the buffer (<= buf_size)
         *      read_size  = return result of the actual number of bytes read 
         */
        int readFile(PVOID p_read_buf, UINT32 data_size, UINT32 &read_size) {
            if (pFile_m==NULL) {
                printf("readFile() - null file pointer, aborting\n");
                return FAILURE;
            }
            // write to file
            char *p_byte = (char*)p_read_buf;
            read_size = fread(p_read_buf,sizeof(char),data_size,pFile_m);

            if (read_size == 0) {
                if (feof(pFile_m) != 0) {
                    return END_OF_FILE;
                } else {
                    return FAILURE;
                }
            }
            return SUCCESS;
        }

    };
#endif  // Linux/Win32

#endif  // FILE_IO_H
