/* grid.h -- code for drawing grid view.  -*- C++ -*-
   Copyright (C) 2025-2026 Gerardo Ballabio

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <functional>
#include <iostream>
#include <thread>
#include <vector>
#include <gtkmm.h>
#include <epoxy/gl.h>

#ifndef __GRID_
#define __GRID_

class gl_grid
{
private:
  std::function<double(double, double, double)> f;

  // data vectors
  std::vector<GLfloat> vertices, surfaces, vertices_bar;
  std::vector<GLuint> indices, indices_s, indices_i;

  // grid parameters
  GLuint size[3], step1[3], step2[3], n_surfaces;
  GLuint n_bar, n_vertices;
  GLuint n_indices[3], n_planes[3], n_slices[3], n_stride[3];
  GLdouble scale1, scale2, scale_margin;
  GLdouble vmax, vscale, ascale;

  // parameters being updated
  GLuint new_size[3], new_step1[3], new_step2[3], new_n_surfaces;
  GLuint new_n_indices[3], new_n_planes[3], new_n_stride[3];
  GLdouble new_scale, new_vmax, new_vscale;
  std::vector<GLuint> new_indices_i;
  bool fix_new_scale, fix_vmax;

  bool refresh_grid, refresh_values, refresh_indices, refresh_surfaces;
  bool refresh_vbuffer, refresh_ibuffer;

  GLdouble view_matrix[16], model_matrix[16];
  unsigned axis;
  bool reverse;
  GLdouble cos;

  // handles for OpenGL objects
  GLuint h_program, h_vertex_shader, h_fragment_shader,
    h_vertex_array, h_vertex_buffer, h_index_buffer,
    h_vertex_array_bar, h_vertex_buffer_bar, h_index_buffer_bar;
  // uniform variables
  GLuint u_matrix, u_value, u_alpha, u_zero, u_scale;

  std::thread *worker;
  bool working_grid, working_values, working_indices, working_surfaces;
  bool stop_worker;
  unsigned nthreads;
  unsigned surfaces_step;

  // custom signals
  sigc::signal<void(bool)> working_signal;
  sigc::signal<void(double)> values_signal;
  sigc::signal<void(double)> scale_signal;

  // display modes
  bool draw_surfaces, draw_volume, draw_bar, clip;

  int width, height, bar_size;

  bool compute_grid();
  void compute_grid_sub(const GLuint size_[3], unsigned m, unsigned n);
  bool compute_values();
  void compute_values_sub(const GLuint size_[3], unsigned m, unsigned n);
  bool compute_indices();
  void compute_indices_sub
  (const GLuint size_[3], const GLuint step1_[3], const GLuint step2_[3],
   unsigned m, unsigned n);
  bool compute_surfaces();
  void compute_surfaces_sub
  (const GLuint size_[3], const GLuint step1_[3], const GLuint step2_[3],
   const GLuint n_indices_[3], GLuint n_surfaces_, unsigned m, unsigned n);
  void compute_surfaces_cube(std::vector<GLfloat> &sub_surfaces,
			     std::vector<GLuint> &sub_indices_s,
			     unsigned &l, const GLuint p[8], const int s[8],
			     int min, int max, GLfloat base, GLfloat step);
  void copy_values();
  void copy_indices();

  void worker_grid();
  void worker_values();
  void worker_indices();
  void worker_surfaces();
  void terminate_worker();

  void refresh();
public:
  gl_grid();
  ~gl_grid();

  sigc::signal<void(bool)> signal_working() { return working_signal; }
  sigc::signal<void(double)> signal_values() { return values_signal; }
  sigc::signal<void(double)> signal_scale() { return scale_signal; }

  void init();
  void init_size(const GLuint size_[3]);
  void init_step(const GLuint step1_[3], const GLuint step2_[3]);
  void init_grid(const GLuint size_[3], const GLuint step1_[3],
		 const GLuint step2_[3])
  { init_size(size_); init_step(step1_, step2_); }
  void set_f(std::function<double(double, double, double)> f_) { f = f_; }
  void set_grid(const GLuint size_[3], const GLuint step1_[3],
		const GLuint step2_[3]);
  void set_nsurfaces(unsigned nsurfaces_);
  void set_scale(double scale_);
  void set_new_scale(double scale_);
  void set_scale_margin(double scale_) { scale_margin = scale_; }
  void set_vmax(double vmax_) { new_vmax = vmax_; }
  void set_vscale(double vscale_);
  void set_new_vscale(double vscale_) { new_vscale = vscale_; }
  void set_fix_vmax(bool fix_) { fix_vmax = fix_; }
  void set_ascale(double ascale_) { ascale = ascale_; }
  void set_nthreads(unsigned nthreads_) { nthreads = nthreads_; }
  void set_draw_surfaces(bool draw_) { draw_surfaces = draw_; }
  void set_draw_volume(bool draw_) { draw_volume = draw_; }
  void set_draw_bar(bool draw_) { draw_bar = draw_; }
  void set_clip(bool clip_) { clip = clip_; }
  void set_window_size(int width_, int height_, int size_)
  { width = width_; height = height_; bar_size = size_; }

  const GLuint *get_size() { return size; }
  const GLuint *get_step1() { return step1; }
  const GLuint *get_step2() { return step2; }
  const GLuint get_nsurfaces() { return n_surfaces; }
  const double get_scale() { return scale1 * scale2; }
  const double get_new_scale() { return new_scale; }
  const double get_scale_margin() { return scale_margin; }
  const double get_vmax() { return vmax; }
  const double get_vscale() { return vscale; }
  const double get_ascale() { return ascale; }
  const bool get_fix_vmax() { return fix_vmax; }
  const unsigned get_nthreads() { return nthreads; }
  const bool get_draw_surfaces() { return draw_surfaces; }
  const bool get_draw_volume() { return draw_volume; }
  const bool get_draw_bar() { return draw_bar; }
  const bool get_clip() { return clip; }

  void queue_set_values() { refresh_values = true; }
  void set_values(const GLfloat *values_);
  void set_values(const std::vector<GLfloat> &values_)
  { set_values(&values_[0]); }
  bool read_values(std::istream &is);
  bool read_values(const Glib::ustring &file = "");

  void set_view_matrix(const GLdouble *matrix_);
  void set_model_matrix(const GLdouble *matrix_);

  void draw();
};

#endif /* __GRID_ */
