// Copyright (c) 2023 Dominic Masters
// 
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

#pragma once
#include "assert/assert.hpp"
#include "util/mathutils.hpp"

#if defined(_MSC_VER)
  #include <direct.h>
  #include <windows.h>
  #define getcwd _getcwd
  #define FILE_PATH_SEP '\\'
  #define fileMkdir(path, perms) _mkdir(path)
#elif defined(__GNUC__)
  #include <unistd.h>
  #include <dirent.h>
  #include <sys/stat.h>
  #define FILE_PATH_SEP '/'
  #define fileMkdir(path, perms) mkdir(path, perms)
#endif
#include <errno.h>

#define FILE_BUFFER_SIZE 512

namespace Dawn {
  enum FileMode {
    FILE_MODE_READ,
    FILE_MODE_WRITE
  };

  class File {
    private:
      enum FileMode mode;

    public:
      static std::string normalizeSlashes(std::string str);
      static void mkdirp(std::string path);

      std::string filename;
      size_t length;
      FILE *file = nullptr;

      /**
       * Constructs a new File interface class.
       * 
       * @param filename Filename that you want to interface with.
       */
      File(std::string filename);

      /**
       * Opens a connection to the file.
       * 
       * @param mode File mode to use for this interface.
       * @return True if success, otherwise for failure.
       */
      bool_t open(enum FileMode mode);

      /**
       * Returns whether or not the file connection is currently opened.
       * 
       * @return True if the connection is open, otherwise if it's not.
       */
      bool_t isOpen();

      /**
       * Returns whether or not the file exists. Will open the connection if it
       * does exist.
       * 
       * @return True if exists, otherwsie if it doesn't.
       */
      bool_t exists();

      /**
       * Closes the currently open interface to the file. Done automatically
       * when this object is disposed.
       */
      void close();

      /**
       * Makes all directories above this file's filename.
       * 
       * @return True if successful, otherwise false.
       */
      bool_t mkdirp();

      /**
       * Reads the entire contents of a file to the given output string buffer.
       * This is a bit dangerous since the length of the file isn't checked
       * against the memory to be consumed.
       * 
       * @param out Pointer to a string where the output data will be stored.
       * @return True if the read was successful, otherwise false.
       */
      bool_t readString(std::string *out);

      /**
       * Reads ahead from the current position to a specific needle (character).
       * 
       * @param buffer Buffer to output read chars to.
       * @param max Max length of the buffer / amount of chars to read ahead.
       * @param needle The character (needle) to look for.
       * @return Amount of chars read, or <= 0 on error.
       */
      size_t readAhead(
        char *buffer,
        size_t max,
        char needle
      );

      /**
       * Reads the contents of this file into a given buffer. If buffer is null
       * then the buffer will be allocated and returned.
       * 
       * @param buffer Pointer to buffer to read to.
       * @return The size of the read data.
       */
      size_t readToBuffer(char **buffer);

      /**
       * Reads the contents of this file into a given buffer. 
       * 
       * @param buffer Pointer to buffer to read to.
       * @param max Max length of the buffer.
       * @return Amount of bytes read.
       */
      size_t readRaw(char *buffer, size_t max);

      /**
       * Writes the entire contents of a string to a file.
       * 
       * @param in String to write to this file.
       * @return True if written successfully, otherwise false.
       */
      bool_t writeString(std::string in);

      /**
       * Write raw bytes to the file.
       * 
       * @param data Data to write.
       * @param size Size of the data to write.
       * @return True if written successfully, otherwise false.
       */
      bool_t writeRaw(char *data, size_t size);

      /**
       * Write raw bytes to the file from another file.
       * 
       * @param otherFile Other file to read from.
       * @param length Length of the data to read.
       * @return True if written successfully, otherwise false.
       */
      bool_t copyRaw(File *otherFile, size_t length);

      /**
       * Set the position of the cursor of the file reader.
       * 
       * @param pos Position to set.
       */
      void setPosition(size_t pos);

      /**
       * Get the file name of this file, optionally with the extension.
       * 
       * @param withExtension If true, the extension will be included.
       * @return The file name.
       */
      std::string getFileName(bool_t withExtension = true);
      
      /**
       * Returns the extension of this file.
       * 
       * @return The extension of this file.
       */
      std::string getExtension();

      /**
       * Destruct the File manager.
       */
      ~File();
  };
}