#pragma once

//this file should only be included from <daguerre/image.hpp>

#include <daguerre/fixed-font.hpp>
#include <algorithm>
#include <cstring>

namespace daguerre {

  template <class color_t>
  image<color_t>::image()
  : delete_buffer_on_destruct(false), width(0),
      height(0), buffer(0), buffer_pitch(0) {}

  template <class color_t>
  image<color_t>::image(int width, int height)
  : delete_buffer_on_destruct(true), width(width), height(height),
    buffer(new color_t[width * height]), buffer_pitch(width) {}

  template <class color_t>
  image<color_t>::image(
    int width, int height, color_t *buffer,
    int buffer_pitch, bool delete_buffer_on_destruct)
  : delete_buffer_on_destruct(delete_buffer_on_destruct), width(width),
    height(height), buffer(buffer), buffer_pitch(buffer_pitch) {}

  template <class color_t>
  image<color_t>::image(image<color_t> &&other)
  : delete_buffer_on_destruct(other.delete_buffer_on_destruct),
    width(other.width), height(other.height), buffer(other.buffer),
    buffer_pitch(other.buffer_pitch) {

    other.width = 0;
    other.height = 0;
    other.buffer = 0;

  }

  template<class color_t>
  image<color_t> &image<color_t>::operator =(image<color_t> &&other) {

    if (delete_buffer_on_destruct && buffer)
      delete[] buffer;

    delete_buffer_on_destruct = other.delete_buffer_on_destruct;
    width = other.width;
    height = other.height;
    buffer = other.buffer;
    buffer_pitch = other.buffer_pitch;

    other.width = 0;
    other.height = 0;
    other.buffer = 0;

    return *this;

  }

  template <class color_t>
  template <class other_color_t>
  image<color_t>::image(const image<other_color_t> &other,
    converter_t<color_t, other_color_t> *conversion)
  : delete_buffer_on_destruct(true), width(other.width),
    height(other.height), buffer(new color_t[other.width * other.height]),
    buffer_pitch(other.width) {

    for (int y = 0; y < height; ++y)
      for (int x = 0; x < width; ++x)
        conversion(
          buffer[y * buffer_pitch + x],
          other.buffer[y * other.buffer_pitch + x]);

  }

  template <class color_t>
  image<color_t>::image(const image<color_t> &other, bool)
  : delete_buffer_on_destruct(true), width(width), height(other.height),
    buffer(new color_t[other.width * other.height]),
    buffer_pitch(other.width) {

    if (buffer_pitch == other.buffer_pitch)
      memcpy(buffer, other.buffer, (height - 1) * buffer_pitch + width);

    else
      for (int y = 0; y < height; ++y)
        memcpy(buffer + y * buffer_pitch,
          other.buffer + y * other.buffer_pitch,
          sizeof(color_t) * width);

  }

  template <class color_t>
  image<color_t>::~image() {
    if (delete_buffer_on_destruct && buffer)
      delete[] buffer;
  }

  template <class color_t>
  void image<color_t>::fill(const color_t &color) {
    for (int y = 0; y < height; ++y)
      for (int x = 0; x < width; ++x)
        buffer[y * buffer_pitch + x] = color;
  }

  template <class color_t>
  void image<color_t>::fill(
    const color_t &color, int start_x, int start_y, int width, int height) {
    for (int y = start_y; y < start_y + height; ++y)
      for (int x = start_x; x < start_x + width; ++x)
        buffer[y * buffer_pitch + x] = color;
  }

  template <class color_t>
  color_t &image<color_t>::at(int x, int y) {
    return buffer[y * buffer_pitch + x];
  }

  template <class color_t>
  const color_t &image<color_t>::at(int x, int y) const {
    return buffer[y * buffer_pitch + x];
  }

  template <class color_t>
  image<color_t> image<color_t>::stretch(int new_width, int new_height) {
    image<color_t> im(new_width, new_height);
    for (int y = 0; y < new_height; ++y)
      for (int x = 0; x < new_width; ++x)
        im.at(x, y) = at(x * width / new_width, y * height / new_height);
    return im;
  }

  template <class color_t>
  void image<color_t>::copy_from(
    const image<color_t> &other, int to_x, int to_y,
    int from_x, int from_y, int width, int height) {

    color_t *to = buffer + to_y * buffer_pitch + to_x;
    const color_t *from = other.buffer + from_y * other.buffer_pitch + from_x;

    for (int y = 0; y < height; ++y)
      memcpy(
        to + y * buffer_pitch,
        from + y * other.buffer_pitch,
        width * sizeof(color_t));

  }

  template <class color_t>
  void image<color_t>::copy_from(
    const image<color_t> &other, int to_x, int to_y) {
    copy_from(other, to_x, to_y, 0, 0, other.width, other.height);
  }

  template <class color_t>
  template <class other_color_t>
  void image<color_t>::convert_from(
    const image<other_color_t> &other, int to_x, int to_y,
    int from_x, int from_y, int width, int height,
    converter_t<color_t, other_color_t> *conversion) {

    for (int y = 0; y < height; ++y)
      for (int x = 0; x < width; ++x)
        conversion(at(to_x + x, to_y + y), other.at(from_x + x, from_y + y));

  }

  template <class color_t>
  template <class other_color_t>
  void image<color_t>::convert_from(
    const image<other_color_t> &other, int to_x, int to_y,
    converter_t<color_t, other_color_t> *conversion) {
    convert_from(other, to_x, to_y, 0, 0, other.width, other.height, conversion);
  }

  template <class color_t>
  template <class other_color_t, class param_t>
  void image<color_t>::convert_from(
    const param_t &param, const image<other_color_t> &other, int to_x,
    int to_y, int from_x, int from_y, int width, int height,
    param_converter_t<color_t, other_color_t, param_t> *conversion) {

    for (int y = 0; y < height; ++y)
      for (int x = 0; x < width; ++x)
        conversion(
          at(to_x + x, to_y + y), other.at(from_x + x, from_y + y), param);

  }

  template <class color_t>
  template <class other_color_t, class param_t>
  void image<color_t>::convert_from(
    const param_t &param, const image<other_color_t> &other, int to_x,
    int to_y, param_converter_t<color_t, other_color_t, param_t> *conversion) {
    convert_from(
      param, other, to_x, to_y, 0, 0, other.width, other.height, conversion);
  }

  template <class color_t>
  template <class font_color_t, class param_t>
  void image<color_t>::render_text(
    const fixed_font<font_color_t> &font,
    const param_t &param, int x, int y, const char *text,
    param_converter_t<color_t, font_color_t, param_t> *conversion) {

    while (*text) {
      int ch = *text;
      if (ch >= 0 && ch < 128)
        convert_from(param, font.glyphs[ch], x, y, conversion);
      ++text;
      x += font.glyph_width;
    }

  }

  template <class color_t>
  template <class font_color_t>
  void image<color_t>::render_text(
    const fixed_font<font_color_t> &font, int x, int y, const char *text,
    converter_t<color_t, font_color_t> *conversion) {

    while (*text) {
      int ch = *text;
      if (ch >= 0 && ch < 128)
        convert_from(font.glyphs[ch], x, y, conversion);
      ++text;
      x += font.glyph_width;
    }

  }

  template <class color_t>
  void swap(image<color_t> &a, image<color_t> &b) {
    std::swap(a.delete_buffer_on_destruct, b.delete_buffer_on_destruct);
    std::swap(a.width, b.width);
    std::swap(a.height, b.height);
    std::swap(a.buffer, b.buffer);
    std::swap(a.buffer_pitch, b.buffer_pitch);
  }

}
