// File: crn_cfile_stream.h
// See Copyright Notice and license at the end of inc/crnlib.h
#pragma once
#include "crn_data_stream.h"

namespace crnlib
{
   class cfile_stream : public data_stream
   {
   public:
      cfile_stream() : data_stream(), m_pFile(NULL), m_size(0), m_ofs(0), m_has_ownership(false)
      {
      }

      cfile_stream(FILE* pFile, const char* pFilename, uint attribs, bool has_ownership) :
         data_stream(), m_pFile(NULL), m_size(0), m_ofs(0), m_has_ownership(false)
      {
         open(pFile, pFilename, attribs, has_ownership);
      }

      cfile_stream(const char* pFilename, uint attribs = cDataStreamReadable | cDataStreamSeekable, bool open_existing = false) :
         data_stream(), m_pFile(NULL), m_size(0), m_ofs(0), m_has_ownership(false)
      {
         open(pFilename, attribs, open_existing);
      }

      virtual ~cfile_stream()
      {
         close();
      }

      virtual bool close()
      {
         clear_error();

         if (m_opened)
         {
            bool status = true;
            if (m_has_ownership)
            {
               if (EOF == fclose(m_pFile))
                  status = false;
            }

            m_pFile = NULL;
            m_opened = false;
            m_size = 0;
            m_ofs = 0;
            m_has_ownership = false;

            return status;
         }

         return false;
      }

      bool open(FILE* pFile, const char* pFilename, uint attribs, bool has_ownership)
      {
         CRNLIB_ASSERT(pFile);
         CRNLIB_ASSERT(pFilename);

         close();

         set_name(pFilename);
         m_pFile = pFile;
         m_has_ownership = has_ownership;
         m_attribs = static_cast<uint16>(attribs);

         m_ofs = crn_ftell(m_pFile);
         crn_fseek(m_pFile, 0, SEEK_END);
         m_size = crn_ftell(m_pFile);
         crn_fseek(m_pFile, m_ofs, SEEK_SET);

         m_opened = true;

         return true;
      }

      bool open(const char* pFilename, uint attribs = cDataStreamReadable | cDataStreamSeekable, bool open_existing = false)
      {
         CRNLIB_ASSERT(pFilename);

         close();

         m_attribs = static_cast<uint16>(attribs);

         const char* pMode;
         if ((is_readable()) && (is_writable()))
            pMode = open_existing ? "r+b" : "w+b";
         else if (is_writable())
            pMode = open_existing ? "ab" : "wb";
         else if (is_readable())
            pMode = "rb";
         else
         {
            set_error();
            return false;
         }

         FILE* pFile = NULL;
         crn_fopen(&pFile, pFilename, pMode);
         m_has_ownership = true;

         if (!pFile)
         {
            set_error();
            return false;
         }

         // TODO: Change stream class to support UCS2 filenames.

         return open(pFile, pFilename, attribs, true);
      }

      FILE* get_file() const { return m_pFile; }

      virtual uint read(void* pBuf, uint len)
      {
         CRNLIB_ASSERT(pBuf && (len <= 0x7FFFFFFF));

         if (!m_opened || (!is_readable()) || (!len))
            return 0;

         len = static_cast<uint>(math::minimum<uint64>(len, get_remaining()));

         if (fread(pBuf, 1, len, m_pFile) != len)
         {
            set_error();
            return 0;
         }

         m_ofs += len;
         return len;
      }

      virtual uint write(const void* pBuf, uint len)
      {
         CRNLIB_ASSERT(pBuf && (len <= 0x7FFFFFFF));

         if (!m_opened || (!is_writable()) || (!len))
            return 0;

         if (fwrite(pBuf, 1, len, m_pFile) != len)
         {
            set_error();
            return 0;
         }

         m_ofs += len;
         m_size = math::maximum(m_size, m_ofs);

         return len;
      }

      virtual bool flush()
      {
         if ((!m_opened) || (!is_writable()))
            return false;

         if (EOF == fflush(m_pFile))
         {
            set_error();
            return false;
         }

         return true;
      }

      virtual uint64 get_size()
      {
         if (!m_opened)
            return 0;

         return m_size;
      }

      virtual uint64 get_remaining()
      {
         if (!m_opened)
            return 0;

         CRNLIB_ASSERT(m_ofs <= m_size);
         return m_size - m_ofs;
      }

      virtual uint64 get_ofs()
      {
         if (!m_opened)
            return 0;

         return m_ofs;
      }

      virtual bool seek(int64 ofs, bool relative)
      {
         if ((!m_opened) || (!is_seekable()))
            return false;

         int64 new_ofs = relative ? (m_ofs + ofs) : ofs;
         if (new_ofs < 0)
            return false;
         else if (static_cast<uint64>(new_ofs) > m_size)
            return false;

         if (static_cast<uint64>(new_ofs) != m_ofs)
         {
            if (crn_fseek(m_pFile, new_ofs, SEEK_SET) != 0)
            {
               set_error();
               return false;
            }

            m_ofs = new_ofs;
         }

         return true;
      }

      static bool read_file_into_array(const char* pFilename, vector<uint8>& buf)
      {
         cfile_stream in_stream(pFilename);
         if (!in_stream.is_opened())
            return false;
         return in_stream.read_array(buf);
      }

      static bool write_array_to_file(const char* pFilename, const vector<uint8>& buf)
      {
         cfile_stream out_stream(pFilename, cDataStreamWritable|cDataStreamSeekable);
         if (!out_stream.is_opened())
            return false;
         return out_stream.write_array(buf);
      }

   private:
      FILE* m_pFile;
      uint64 m_size, m_ofs;
      bool m_has_ownership;
   };

} // namespace crnlib