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

using fmt::println;

namespace matrix {
  using std::vector, std::queue, std::array;

  typedef vector<int> Row;
  typedef vector<Row> Matrix;

  struct each_cell {
    size_t x = ~0;
    size_t y = ~0;
    size_t width = 0;
    size_t height = 0;

    each_cell(Matrix &mat);
    bool next();
  };

  struct each_row {
    Matrix &$mat;
    size_t x = ~0;
    size_t y = ~0;
    size_t width = 0;
    size_t height = 0;
    bool row = false;

    each_row(Matrix &mat);
    bool next();
  };

  struct in_box {
    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;

    in_box(Matrix &mat, size_t x, size_t y, size_t size);
    float distance();
    bool next();
    void dump();
  };

  struct compass {
    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(Matrix &mat, size_t x, size_t y);
    bool next();
  };

  /*
   * Just a quick thing to reset a matrix to a value.
   */
  inline void assign(Matrix &out, int new_value) {
    for(auto &row : out) {
      row.assign(row.size(), new_value);
    }
  }

  inline bool inbounds(Matrix &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;
  }

  inline size_t width(Matrix &mat) {
    return mat[0].size();
  }

  inline size_t height(Matrix &mat) {
    return mat.size();
  }

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

  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();
  };

  struct circle {
    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(Matrix &mat, Point center, float radius);
    void update();
    bool next();
  };
}
