#pragma once
#include <vector>
#include <queue>
#include <string>
#include <array>
#include <fmt/core.h>
#include "point.hpp"

namespace matrix {
  using std::vector, std::queue, std::array;
  using std::min, std::max, std::floor;

  template<typename T>
  using BaseRow = vector<T>;

  template<typename T>
  using Base = vector<BaseRow<T>>;

  using Row = vector<int>;
  using Matrix = vector<Row>;


  /*
   * Just a quick thing to reset a matrix to a value.
   */
  template<typename MAT, typename VAL>
  inline void assign(MAT &out, VAL new_value) {
    for(auto &row : out) {
      row.assign(row.size(), new_value);
    }
  }

  template<typename MAT>
  inline bool inbounds(MAT &mat, size_t x, size_t y) {
    // since Point.x and Point.y are size_t any negatives are massive
    bool res = (y < mat.size()) && (x < mat[0].size());
    return res;
  }

  template<typename MAT>
  inline size_t width(MAT &mat) {
    return mat[0].size();
  }

  template<typename MAT>
  inline size_t height(MAT &mat) {
    return mat.size();
  }

  template<typename T>
  inline Base<T> make_base(size_t width, size_t height) {
    Base<T> result(height, BaseRow<T>(width));
    return result;
  }

  inline Matrix make(size_t width, size_t height) {
    Matrix result(height, Row(width));
    return result;
  }

  inline size_t next_x(size_t x, size_t width) {
    return (x + 1) * ((x + 1) < width);
  }

  inline size_t next_y(size_t x, size_t y) {
    return y + (x == 0);
  }

  inline bool at_end(size_t y, size_t height) {
    return y < height;
  }

  inline bool end_row(size_t x, size_t width) {
    return x == width - 1;
  }

  void dump(const std::string &msg, Matrix &map, int show_x=-1, int show_y=-1);

  template<typename MAT>
  struct each_cell_t {
    size_t x = ~0;
    size_t y = ~0;
    size_t width = 0;
    size_t height = 0;

    each_cell_t(MAT &mat)
    {
      height = matrix::height(mat);
      width = matrix::width(mat);
    }

    bool next() {
      x = next_x(x, width);
      y = next_y(x, y);
      return at_end(y, height);
    }
  };

  template<typename MAT>
  struct viewport_t {
    Point start;
    // this is the point in the map
    size_t x;
    size_t y;
    // this is the point inside the box, start at 0
    size_t view_x = ~0;
    size_t view_y = ~0;
    // viewport width/height
    size_t width;
    size_t height;

    viewport_t(MAT &mat, Point start, int max_x, int max_y) :
      start(start),
      x(start.x-1),
      y(start.y-1)
    {
      width = std::min(size_t(max_x), matrix::width(mat) - start.x);
      height = std::min(size_t(max_y), matrix::height(mat) - start.y);
      fmt::println("viewport_t max_x, max_y {},{} vs matrix {},{}, x={}, y={}",
          max_x, max_y, matrix::width(mat), matrix::height(mat), x, y);
    }

    bool next() {
      y = next_y(x, y);
      x = next_x(x, width);
      view_x = next_x(view_x, width);
      view_y = next_y(view_x, view_y);
      return at_end(y, height);
    }
  };

  using viewport = viewport_t<Matrix>;

  using each_cell = each_cell_t<Matrix>;

  template<typename MAT>
  struct each_row_t {
    size_t x = ~0;
    size_t y = ~0;
    size_t width = 0;
    size_t height = 0;
    bool row = false;

    each_row_t(MAT &mat) {
      height = matrix::height(mat);
      width = matrix::width(mat);
    }

    bool next() {
      x = next_x(x, width);
      y = next_y(x, y);
      row = end_row(x, width);
      return at_end(y, height);
    }
  };

  using each_row = each_row_t<Matrix>;

  template<typename MAT>
  struct box_t {
    size_t from_x;
    size_t from_y;
    size_t x = 0; // these are set in constructor
    size_t y = 0; // again, no fancy ~ trick needed
    size_t left = 0;
    size_t top = 0;
    size_t right = 0;
    size_t bottom = 0;

    box_t(MAT &mat, size_t at_x, size_t at_y, size_t size) :
      box_t(mat, at_x, at_y, size, size) {
      }

    box_t(MAT &mat, size_t at_x, size_t at_y, size_t width, size_t height) :
      from_x(at_x), from_y(at_y)
    {
      size_t h = matrix::height(mat);
      size_t w = matrix::width(mat);

      // keeps it from going below zero
      // need extra -1 to compensate for the first next()
      left = max(from_x, width) - width;
      x = left - 1;  // must be -1 for next()
      // keeps it from going above width
      right = min(from_x + width + 1, w);

      // same for these two
      top = max(from_y, height) - height;
      y = top - (left == 0);
      bottom = min(from_y + height + 1, h);
    }

    bool next() {
      // calc next but allow to go to 0 for next
      x = next_x(x, right);
      // x will go to 0, which signals new line
      y = next_y(x, y);  // this must go here
      // if x==0 then this moves it to min_x
      x = max(x, left);
      // and done

      return at_end(y, bottom);
    }

    float distance() {
      int dx = from_x - x;
      int dy = from_y - y;

      return sqrt((dx * dx) + (dy * dy));
    }
  };

  using box = box_t<Matrix>;

  template<typename MAT>
  struct compass_t {
    size_t x = 0; // these are set in constructor
    size_t y = 0; // again, no fancy ~ trick needed
    array<int, 4> x_dirs{0, 1, 0, -1};
    array<int, 4> y_dirs{-1, 0, 1, 0};
    size_t max_dirs=0;
    size_t dir = ~0;

    compass_t(MAT &mat, size_t x, size_t y) :
      x(x), y(y)
    {
      array<int, 4> x_in{0, 1, 0, -1};
      array<int, 4> y_in{-1, 0, 1, 0};

      for(size_t i = 0; i < 4; i++) {
        int nx = x + x_in[i];
        int ny = y + y_in[i];
        if(matrix::inbounds(mat, nx, ny)) {
          x_dirs[max_dirs] = nx;
          y_dirs[max_dirs] = ny;
          max_dirs++;
        }
      }
    }

    bool next() {
      dir++;
      if(dir < max_dirs) {
        x = x_dirs[dir];
        y = y_dirs[dir];
        return true;
      } else {
        return false;
      }
    }
  };

  using compass = compass_t<Matrix>;

  struct flood {
    Matrix &mat;
    Point start;
    int old_val;
    int new_val;
    queue<Point> q;
    Point current_loc;
    int x;
    int y;
    matrix::compass dirs;

    flood(Matrix &mat, Point start, int old_val, int new_val);
    bool next();
    bool next_working();
  };

  struct line {
    int x;
    int y;
    int x1;
    int y1;
    int sx;
    int sy;
    int dx;
    int dy;
    int error;

    line(Point start, Point end);
    bool next();
  };

  template<typename MAT>
  struct circle_t {
    float center_x;
    float center_y;
    float radius = 0.0f;
    int y = 0;
    int dx = 0;
    int dy = 0;
    int left = 0;
    int right = 0;
    int top = 0;
    int bottom = 0;
    int width = 0;
    int height = 0;

    circle_t(MAT &mat, Point center, float radius) :
      center_x(center.x), center_y(center.y), radius(radius)
    {
      width = matrix::width(mat);
      height = matrix::height(mat);
      top = max(int(floor(center_y - radius)), 0);
      bottom = min(int(floor(center_y + radius)), height - 1);

      y = top;
    }

    bool next() {
      y++;
      if(y <= bottom) {
        dy = y - center_y;
        dx = floor(sqrt(radius * radius - dy * dy));
        left = max(0, int(center_x) - dx);
        right = min(width, int(center_x) + dx + 1);
        return true;
      } else {
        return false;
      }
    }
  };

  using circle = circle_t<Matrix>;
}
