#include "plotwidget.h"

#include <gtkmm/eventcontrollermotion.h>
#include <gtkmm/eventcontrollerfocus.h>
#include <gtkmm/eventcontrollerscroll.h>
#include <gtkmm/gestureclick.h>

PlotWidget::PlotWidget()
    : _plot(nullptr),
      _mouseIsIn(false),
      _mouseX(0),
      _mouseY(0),
      _isButtonPressed(false),
      _isZooming(false),
      _isPanning(false),
      _bpressStartX(0),
      _bpressStartY(0) {
  std::shared_ptr<Gtk::EventControllerMotion> motion =
      Gtk::EventControllerMotion::create();
  motion->signal_motion().connect(sigc::mem_fun(*this, &PlotWidget::onMotion));
  add_controller(motion);

  std::shared_ptr<Gtk::EventControllerFocus> focus =
      Gtk::EventControllerFocus::create();
  focus->signal_leave().connect(sigc::mem_fun(*this, &PlotWidget::onLeave));
  add_controller(focus);

  std::shared_ptr<Gtk::GestureClick> left_click = Gtk::GestureClick::create();
  left_click->set_button(1);
  left_click->signal_pressed().connect(
      [this](int, double x, double y) { PlotWidget::onLeftButtonPress(x, y); });
  left_click->signal_released().connect(
      [this](int, double x, double y) { PlotWidget::onButtonRelease(x, y); });
  add_controller(left_click);

  std::shared_ptr<Gtk::GestureClick> right_click = Gtk::GestureClick::create();
  right_click->set_button(3);
  right_click->signal_pressed().connect([this](int, double x, double y) {
    PlotWidget::onRightButtonPress(x, y);
  });
  right_click->signal_released().connect(
      [this](int, double x, double y) { PlotWidget::onButtonRelease(x, y); });
  add_controller(right_click);

  std::shared_ptr<Gtk::EventControllerScroll> zoom =
      Gtk::EventControllerScroll::create();
  zoom->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
  zoom->signal_scroll().connect(sigc::mem_fun(*this, &PlotWidget::onScroll),
                                false);

  set_draw_func([this](const Cairo::RefPtr<Cairo::Context>& cr, double w,
                       double h) { return PlotWidget::onDraw(cr); });
  set_size_request(300, 200);
}

PlotWidget::~PlotWidget() { _linkedRedrawConnection.disconnect(); }

void PlotWidget::onDraw(const Cairo::RefPtr<Cairo::Context>& cr) {
  _beforeDrawSignal();
  if (get_width() > 0 && get_height() > 0) {
    if (_plot != nullptr) {
      _plot->Draw(cr, get_width(), get_height());
      if (_isZooming) {
        cr->set_line_width(1.0);
        cr->rectangle(_bpressStartX, _bpressStartY, _mouseX - _bpressStartX,
                      _mouseY - _bpressStartY);
        cr->set_source_rgba(0.35, 0.35, 1.0, 0.4);
        cr->fill_preserve();
        cr->set_source_rgb(0.0, 0.0, 0.0);
        cr->stroke();
      }
    } else {
      cr->set_source_rgba(1, 1, 1, 1);
      cr->paint();
      cr->fill();
    }
  }
}

void PlotWidget::Update() { queue_draw(); }

void PlotWidget::onMotion(double x, double y) {
  if (_plot) {
    double posX, posY;
    const bool isInside = _plot->ConvertToPlot(x, y, posX, posY);
    if (_isZooming) {
      _mouseX = x;
      _mouseY = y;
      Update();
    } else if (_isPanning) {
      _plot->Pan(x - _mouseX, y - _mouseY);
      _mouseX = x;
      _mouseY = y;
      Update();
    } else {
      if (isInside) {
        _mouseX = x;
        _mouseY = y;
        _mouseIsIn = true;
        _onMouseMoved(posX, posY);
      } else if (_mouseIsIn) {
        _onMouseLeft();
        _mouseIsIn = false;
      }
    }
  }
}

void PlotWidget::onLeave() {
  if (_mouseIsIn) {
    _onMouseLeft();
    _mouseIsIn = false;
  }
}

void PlotWidget::onLeftButtonPress(double x, double y) {
  _isButtonPressed = true;
  if (_plot) {
    double posX, posY;
    if (_plot->ConvertToPlot(x, y, posX, posY)) {
      _mouseX = x;
      _mouseY = y;
      _bpressStartX = _mouseX;
      _bpressStartY = _mouseY;
      _isZooming = true;
    }
  }
}

void PlotWidget::onRightButtonPress(double x, double y) {
  _isButtonPressed = true;
  if (_plot) {
    double posX, posY;
    if (_plot->ConvertToPlot(x, y, posX, posY)) {
      _mouseX = x;
      _mouseY = y;
      _bpressStartX = _mouseX;
      _bpressStartY = _mouseY;
      _isPanning = true;
    }
  }
}

void PlotWidget::onButtonRelease(double x, double y) {
  _isButtonPressed = false;
  if (_plot) {
    const double oldMouseX = _mouseX;
    const double oldMouseY = _mouseY;
    double posX, posY;
    if (_plot->ConvertToPlot(x, y, posX, posY)) {
      _mouseX = x;
      _mouseY = y;
      _onButtonReleased(posX, posY);
    }
    if (_isZooming) {
      _isZooming = false;
      if (_bpressStartX != _mouseX || _bpressStartY != _mouseY) {
        double startX, startY;
        _plot->ConvertToPlot(_bpressStartX, _bpressStartY, startX, startY);
        _plot->ZoomTo(startX, startY, posX, posY);
      }
    }
    if (_isPanning) {
      _isPanning = false;
      _plot->Pan(oldMouseX - x, oldMouseY - y);
    }

    Update();
  }
}

bool PlotWidget::onScroll(double x, double y) {
  if (_plot) {
    int direction = 0;
    if (y < 0.0)
      direction = -1;
    else
      direction = 1;
    _onScroll(0.0, 0.0, direction);
  }
  return true;
}
