/* area.h -- OpenGL window for plotting a 3D function.  -*- 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 <iostream>
#include <string>
#include <gtkmm.h>
#include <epoxy/gl.h>
#include "frame.h"
#include "grid.h"

#ifndef __AREA_
#define __AREA_

class Area : public Gtk::GLArea
{
private:
  double vscale;

  GLuint size[3], step1[3], step2[3];

  double x0, y0;
  bool button[5]; // Gdk mouse buttons

  double fps;
  sigc::connection timeout;
protected:
  double scale;
  double model_matrix[16], view_matrix[16];
  GLfloat matrix[16];

  int width, height, side;

  gl_frame frame;
  gl_grid grid;

  virtual void on_realize();
  virtual bool on_render(const Glib::RefPtr<Gdk::GLContext> &context);
  virtual bool on_button_press_event(GdkEventButton *event);
  virtual bool on_button_release_event(GdkEventButton *event);
  virtual bool on_motion_notify_event(GdkEventMotion *event);
  virtual bool on_timeout() { queue_draw(); return true; }
  virtual void on_hide() { timeout.disconnect(); }
  virtual void on_grid_signal_scale(double scale_);
  virtual void resize(int width_, int height_);
  virtual void draw();
  void init_model_matrix();

  // custom signals
  sigc::signal<void(void)> press_signal;
  sigc::signal<void(double)> fps_signal;
public:
  Area();
  virtual ~Area() { }

  sigc::signal<void(bool)> signal_working() { return grid.signal_working(); }
  sigc::signal<void(double)> signal_values() { return grid.signal_values(); }
  sigc::signal<void(void)> signal_press() { return press_signal; }
  sigc::signal<void(double)> signal_fps() { return fps_signal; }

  void set_f(std::function<double(double, double, double)> f_)
  { grid.set_f(f_); }

  void rotate(double x, double z, double x0, double z0, double l);
  void set_nsurfaces(unsigned nsurfaces_) { grid.set_nsurfaces(nsurfaces_); }
  void set_scale(double scale_);
  void set_new_scale(double scale_) { grid.set_new_scale(scale_); }
  void set_scale_margin(double scale_) { grid.set_scale_margin(scale_); }
  void set_vmax(double vmax_) { grid.set_vmax(vmax_); }
  void set_vscale(double vscale_);
  void set_new_vscale(double vscale_) { grid.set_new_vscale(vscale_); }
  void set_ascale(double ascale_) { grid.set_ascale(ascale_); }
  void set_fix_vmax(bool fix_) { grid.set_fix_vmax(fix_); }
  void set_nthreads(unsigned nthreads_) { grid.set_nthreads(nthreads_); }
  void set_draw_surfaces(bool draw_) { grid.set_draw_surfaces(draw_); }
  void set_draw_volume(bool draw_) { grid.set_draw_volume(draw_); }
  void set_clip(bool clip_) { grid.set_clip(clip_); }
  void set_draw_axes(bool draw_) { frame.set_draw_axes(draw_); }
  void set_draw_cube(bool draw_) { frame.set_draw_cube(draw_); }
  void set_draw_bar(bool draw_)
  { frame.set_draw_bar(draw_); grid.set_draw_bar(draw_); }
  void set_grid(const GLuint size[3], const GLuint step1[3],
		const GLuint step2[3])
  { grid.set_grid(size, step1, step2); }
  void init_size(const GLuint size[3]) { grid.init_size(size); }
  void init_step(const GLuint step1[3], const GLuint step2[3])
  { grid.init_step(step1, step2); }
  void init_grid(const GLuint size[3], const GLuint step1[3],
		 const GLuint step2[3])
  { grid.init_grid(size, step1, step2); }

  const GLuint *get_size() { return grid.get_size(); }
  const GLuint *get_step1() { return grid.get_step1(); }
  const GLuint *get_step2() { return grid.get_step2(); }
  const unsigned get_nsurfaces() { return grid.get_nsurfaces(); }
  const double get_scale() { return scale; }
  const double get_new_scale() { return grid.get_new_scale(); }
  const double get_scale_margin() { return grid.get_scale_margin(); }
  const double get_vscale() { return vscale; }
  const double get_ascale() { return grid.get_ascale(); }
  const bool get_fix_vmax() { return grid.get_fix_vmax(); }
  const unsigned get_nthreads() { return grid.get_nthreads(); }
  const bool get_draw_surfaces() { return grid.get_draw_surfaces(); }
  const bool get_draw_volume() { return grid.get_draw_volume(); }
  const bool get_clip() { return grid.get_clip(); }
  const bool get_draw_axes() { return frame.get_draw_axes(); }
  const bool get_draw_cube() { return frame.get_draw_cube(); }
  const bool get_draw_bar() { return frame.get_draw_bar(); }
  const double get_vmax() { return grid.get_vmax(); }
  const double get_fps() { return fps; }

  virtual void reset();
  void queue_set_values() { grid.queue_set_values(); }
  void set_values(const GLfloat *values_) { grid.set_values(values_); }
  void set_values(const std::vector<GLfloat> &values_)
  { grid.set_values(values_); }
  bool read_values(std::istream &is) { return grid.read_values(is); }
  bool read_values(const Glib::ustring &file = "")
  { return grid.read_values(file); }
};

#endif /* __AREA_ */
