/*
  ZynAddSubFX - a software synthesizer

  Fl_Osc_Slider.cpp - OSC Based Slider
  Copyright (C) 2016 Mark McCurry

  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 2
  of the License, or (at your option) any later version.
*/
#include <FL/Fl.H>
#include "Fl_Osc_Slider.H"
#include "Fl_Osc_Interface.h"
#include "Fl_Osc_Pane.H"
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <sstream>
#include "../Misc/Util.h"

static double min__(double a, double b)
{
    return a<b?a:b;
}

Fl_Osc_Slider::Fl_Osc_Slider(int X, int Y, int W, int H, const char *label)
    :Fl_Slider(X,Y,W,H,label), Fl_Osc_Widget(this), reset_value(0),
     cb_data(NULL, NULL), just_pushed(true)
{
    //bounds(0.0f,1.0f);
    Fl_Slider::callback(Fl_Osc_Slider::_cb);
}

void Fl_Osc_Slider::init(std::string path_, char type_)
{
    osc_type = type_;
    ext = path_;
    oscRegister(ext.c_str());
}

Fl_Osc_Slider::~Fl_Osc_Slider(void)
{}

void Fl_Osc_Slider::OSC_value(int v)
{
    const float min_ = min__(minimum(), maximum());//flipped sliders
    Fl_Slider::value(v+min_+value()-floorf(value()));
}

void Fl_Osc_Slider::OSC_value(float v)
{
    const float min_ = min__(minimum(), maximum());//flipped sliders
    Fl_Slider::value(v+min_);
}

void Fl_Osc_Slider::OSC_value(char v)
{
    const float min_ = min__(minimum(), maximum());//flipped sliders
    Fl_Slider::value(v+min_+value()-floorf(value()));
}

void Fl_Osc_Slider::cb(void)
{
    const float min_ = min__(minimum(), maximum());//flipped sliders
    const float val = Fl_Slider::value();
    if(osc_type == 'f')
        oscWrite(ext, "f", val-min_);
    else if(osc_type == 'i')
        oscWrite(ext, "i", (int)(val-min_));
    else {
        fprintf(stderr, "invalid `c' from slider %s%s, using `i'\n", loc.c_str(), ext.c_str());
        oscWrite(ext, "i", (int)(val-min_));
    }
    //OSC_value(val);

    if(cb_data.first)
        cb_data.first(this, cb_data.second);
}

void Fl_Osc_Slider::callback(Fl_Callback *cb, void *p)
{
    cb_data.first = cb;
    cb_data.second = p;
}

#define MOD_MASK (FL_CTRL | FL_SHIFT)

int Fl_Osc_Slider::handle(int ev, int X, int Y, int W, int H)
{
    bool middle_mouse = (ev == FL_PUSH && Fl::event_state(FL_BUTTON2) && !Fl::event_shift());
    bool ctl_click    = (ev == FL_PUSH && Fl::event_state(FL_BUTTON3) && Fl::event_ctrl());
    bool shift_middle = (ev == FL_PUSH && Fl::event_state(FL_BUTTON2) && Fl::event_shift());
    if(middle_mouse || ctl_click) {
        printf("Trying to learn...\n");
        osc->write("/learn", "s", (loc+ext).c_str());
        return 1;
    } else if(shift_middle) {
        osc->write("/unlearn", "s", (loc+ext).c_str());
        return 1;
    }

    int handled;
    float rounded;

    const float range = maximum() - minimum();
    const float absrange = (range > 0 ? range : -range)+1;
    int old_mod_state;
    const float normal_step = range / W;

    switch (ev) {
        case FL_PUSH:
            just_pushed = true;
            mod_state = Fl::event_state() & MOD_MASK;
            slow_state = 0;
            start_pos = horizontal() ? Fl::event_x() : Fl::event_y();
            handled = mod_state ? 1 : Fl_Slider::handle(ev, X, Y, W, H);
            break;
        case FL_MOUSEWHEEL:
            mod_state = Fl::event_state() & MOD_MASK;
            if (Fl::event_buttons())
                return 1;
            if (this == Fl::belowmouse() && Fl::e_dy != 0) {
                int step_ = 1, divisor = 16;

                switch (mod_state) {
                    case FL_SHIFT:
                        if (absrange > divisor * 8)
                            step_ = 8;
                    case FL_SHIFT | FL_CTRL:
                        break;
                    case FL_CTRL:
                        divisor = 128;
                        // fall through
                    default:
                        step_ = absrange / divisor;
                        if (step_ < 1)
                            step_ = 1;
                }
                int dy = minimum() <=  maximum() ? -Fl::e_dy : Fl::e_dy;
                // Flip sense for vertical sliders.
                dy = this->horizontal() ? dy : -dy;
                handle_drag(clamp(value() + step_ * dy));
            }
            return 1;
        case FL_RELEASE:
            handled = Fl_Slider::handle(ev, X, Y, W, H);
            if (Fl::event_clicks() == 1) {
                Fl::event_clicks(0);
                value(reset_value);
            } else {
                rounded = floorf(value() + 0.5);
                value(clamp(rounded));
            }
            value_damage();
            do_callback();
            break;
        case FL_DRAG: {
            old_mod_state = mod_state;
            mod_state = Fl::event_state() & MOD_MASK;
            if (slow_state == 0 && mod_state == 0) {
                int delta = (horizontal() ? Fl::event_x() : Fl::event_y())
                    - start_pos;
                if (delta < -1 || delta > 1)
                    Fl::event_clicks(0);
                return Fl_Slider::handle(ev, X, Y, W, H);
            }

            if (mod_state != 0) {
                slow_state = 1;
            } else if (slow_state == 1)
                slow_state = 2;

            if (just_pushed || old_mod_state != mod_state) {
                just_pushed = false;
                old_value = value();
                start_pos = horizontal() ? Fl::event_x() : Fl::event_y();
                if (slow_state == 1) {
                    denominator = 2.0;
                    float step_ = step();
                    if (step_ == 0) step_ = 1;

                    if (absrange / W / step_ > 32)
                        switch (mod_state) {
                            case FL_CTRL:
                                denominator = 0.15;
                                break;
                            case FL_SHIFT:
                                denominator = 0.7;
                                break;
                            case MOD_MASK:
                                denominator = 3.0;
                                break;
                        }
                    else if (mod_state & FL_SHIFT)
                        denominator = 5.0;

                    if (range < 0)
                        denominator *= -1;
                }
            }

            int delta = (horizontal() ? Fl::event_x() : Fl::event_y())
                - start_pos;
            if (delta < -1 || delta > 1)
                Fl::event_clicks(0);
            float new_value;
            if (slow_state == 1) {
                new_value = old_value + delta / denominator;
            } else {
                new_value = old_value + delta * normal_step;
            }
            const float clamped_value = clamp(new_value);
            rounded = floor(clamped_value + 0.5);
            if (new_value != clamped_value) {
                start_pos = horizontal() ? Fl::event_x() : Fl::event_y();
                old_value = rounded;
                if (slow_state == 2 &&
                    ((horizontal() &&
                      (Fl::event_x() < X || Fl::event_x() > X + W)) ||
                     (!horizontal() &&
                      (Fl::event_y() < Y || Fl::event_y() > Y + H))))
                    slow_state = 0;
            }
            value(rounded);
            value_damage();
            do_callback();

            handled = 1;
            break;
        }
        default:
            handled = Fl_Slider::handle(ev, X, Y, W, H);
    }

    return handled;
}

int Fl_Osc_Slider::handle(int ev) {
    return handle(ev,
                  x()+Fl::box_dx(box()),
                  y()+Fl::box_dy(box()),
                  w()-Fl::box_dw(box()),
                  h()-Fl::box_dh(box()));
}

void Fl_Osc_Slider::update(void)
{
    oscWrite(ext, "");
}

void Fl_Osc_Slider::_cb(Fl_Widget *w, void *)
{
    static_cast<Fl_Osc_Slider*>(w)->cb();
}
