#include <euler/stream.hpp>
#include <cstring>

namespace euler {

  stream::~stream() {}

  void file_stream::write_buffer() {
    if (__euler_write_to_stream(handle, buffer_size, buffer)
        != __EULER_SR_SUCCESS)
      good = false;//TODO: more precise error reporting
    else
      buffer_dirty = false;
  }

  file_stream::file_stream(
    __euler_stream_handle handle, bool is_readable, bool is_writable,
    bool clear, bool seek_to_end)

  : handle(handle), is_readable(is_readable), is_writable(is_writable),
    buffer(0), good(true) {

    if (clear) {
      if (__euler_set_stream_length(handle, 0) != __EULER_SR_SUCCESS) {
        good = false;
        return;
      }
      length = 0;
    }

    else if (__euler_get_stream_length(handle, length) != __EULER_SR_SUCCESS) {
      good = false;
      return;
    }

    if (seek_to_end) {
      if (__euler_seek_stream(handle, __EULER_SF_END, 0)
          != __EULER_SR_SUCCESS) {
        good = false;
        return;
      }
      offset = length;
    }

    else
      offset = 0;

  }

  file_stream::~file_stream() {
    if (buffer) {
      if (buffer_dirty)
        write_buffer();
      delete[] buffer;
    }
    __euler_close_stream(handle);
  }

  bool file_stream::try_read(void *into, uint64_t bytes) {

    if (bytes == 0)
      return true;

    if (offset + bytes > length)
      return false;

    if (buffer) {
      uint64_t start = offset > buffer_offset ? offset : buffer_offset;
      uint64_t end = offset + bytes < buffer_offset + buffer_size
              ? offset + bytes : buffer_offset + buffer_size;
      if (end > start) {

        uint64_t real_end = offset + bytes;

        std::memcpy(
          (uint8_t *)into + start - offset, buffer + start - buffer_offset,
          end - start);
        if (start != offset)
          if (!try_read(into, start - offset))
            return false;

        if (end != real_end) {
          if (!try_seek(__EULER_SF_BEGINNING, end))
            return false;
          if (!try_read((uint8_t *)into + end, real_end - end))
            return false;
        }

        else if (offset != real_end)
          if (!try_seek(__EULER_SF_BEGINNING, real_end))
            return false;

        return true;

      }
    }

    if (buffer_dirty) {
      write_buffer();
      if (!good)
        return false;
    }

    //1024 is arbitrary
    buffer_size = length - offset < 1024 ? length - offset : 1024;

    if (buffer != 0)
      delete[] buffer;

    buffer = new uint8_t[buffer_size];
    buffer_dirty = false;
    buffer_offset = offset;

    if (__euler_read_from_stream(handle, buffer_size, buffer)
        != __EULER_SR_SUCCESS) {
      delete[] buffer;
      buffer = 0;
      return false;
    }

    uint64_t read_this_buffer = buffer_size < bytes ? buffer_size : bytes;
    std::memcpy(into, buffer, read_this_buffer);
    offset += read_this_buffer;

    if (read_this_buffer == bytes)
      return true;

    return try_read(
      (uint8_t *)into + read_this_buffer, bytes - read_this_buffer);

  }

  bool file_stream::try_seek(__euler_seek_from from, int64_t offset) {
    if (__euler_seek_stream(handle, from, offset) != __EULER_SR_SUCCESS)
      return false;
    switch (from) {
    case __EULER_SF_BEGINNING:
      this->offset = offset;
      return true;
    case __EULER_SF_END:
      this->offset = length + offset;
      return true;
    case __EULER_SF_CURRENT_POSITION:
      this->offset += offset;
      return true;
    default:
      return false;
    }
  }

}
