#include <../../nrnconf.h>
#if HAVE_IV  // to end of file

#include <IV-look/kit.h>
#include <InterViews/layout.h>
#include <InterViews/telltale.h>
#include <InterViews/window.h>
#include <OS/list.h>
#include <OS/string.h>
#include <stdio.h>
#include "ivoc.h"
#include "mymath.h"
#include "scenevie.h"
#include "scenepic.h"
#include "rubband.h"
#include "apwindow.h"
#include "utility.h"
#include "oc2iv.h"

#define Scene_SceneMover_     "Translate Scene"
#define Scene_SceneZoom_      "ZoomInOut Scene"
#define Scene_RoundView_      "RoundView Scene"
#define Scene_WholeSceneView_ "WholeScene Scene"
#define Scene_WholePlotView_  "View_equal_Plot Scene"
#define Scene_ZoomOut10_      "ZoomOut10 Scene"
#define Scene_ZoomeIn10_      "ZoomIn10 Scene"
#define Scene_SpecView_       "SetView Scene"
#define Scene_SizeScene_      "Scene_equal_View Scene"
#define Scene_NewView_        "NewView Scene"
#define Scene_ShowMenu_       "Scene"
#define Scene_ObjectName_     "ObjectName"

MenuItem* K::menu_item(const char* name) {
    Glyph* g =
        LayoutKit::instance()->r_margin(WidgetKit::instance()->fancy_label(name), 0.0, fil, 0.0);
    return WidgetKit::instance()->menu_item(g);
}

MenuItem* K::check_menu_item(const char* name) {
    Glyph* g =
        LayoutKit::instance()->r_margin(WidgetKit::instance()->fancy_label(name), 0.0, fil, 0.0);
    return WidgetKit::instance()->check_menu_item(g);
}

MenuItem* K::radio_menu_item(TelltaleGroup* tg, const char* name) {
    Glyph* g =
        LayoutKit::instance()->r_margin(WidgetKit::instance()->fancy_label(name), 0.0, fil, 0.0);
    return WidgetKit::instance()->radio_menu_item(tg, g);
}

/*static*/ class ButtonItemInfo {
  public:
    ButtonItemInfo(const char* name, Action*, TelltaleState*, MenuItem* mi = NULL, Menu* m = NULL);
    virtual ~ButtonItemInfo();
    GlyphIndex menu_index();
    TelltaleState* s_;
    CopyString name_;
    Action* a_;
    Menu* parent_;
    MenuItem* mi_;
};
ButtonItemInfo::ButtonItemInfo(const char* name,
                               Action* a,
                               TelltaleState* s,
                               MenuItem* mi,
                               Menu* parent) {
    name_ = name;
    s_ = s;
    a_ = a;
    parent_ = parent;
    mi_ = mi;
}
ButtonItemInfo::~ButtonItemInfo() {}

GlyphIndex ButtonItemInfo::menu_index() {
    GlyphIndex i, cnt;
    if (parent_ && mi_) {
        cnt = parent_->item_count();
        for (i = 0; i < cnt; ++i) {
            if (parent_->item(i) == mi_) {
                return i;
            }
        }
    }
    return -1;
}

declarePtrList(ButtonItemInfoList, ButtonItemInfo)
implementPtrList(ButtonItemInfoList, ButtonItemInfo)

/*static*/ class SceneMover: public OcHandler {
  public:
    SceneMover();
    virtual ~SceneMover();
    virtual bool event(Event&);
    virtual void help();

  private:
    Coord x_, y_;
    XYView* view_;
};

/*static*/ class SceneZoom: public OcHandler {
  public:
    SceneZoom();
    virtual ~SceneZoom();
    virtual bool event(Event&);
    virtual void help();

  private:
    Coord x_, y_, xorg_, yorg_;
    XYView* view_;
};

/*static*/ class NewView: public RubberAction {
  public:
    NewView();
    virtual ~NewView();
    virtual void execute(Rubberband*);
    virtual void help();
};

/*static*/ class RoundView: public Action { virtual void execute(); };

/*static*/ class WholeSceneView: public Action { virtual void execute(); };

/*static*/ class WholePlotView: public Action { virtual void execute(); };

/*static*/ class SpecView: public Action { virtual void execute(); };

/*static*/ class SizeScene: public Action { virtual void execute(); };

/*static*/ class ZoomOut10: public Action { virtual void execute(); };

/*static*/ class ZoomIn10: public Action { virtual void execute(); };

/*static*/ class SPObjectName: public Action { virtual void execute(); };

/*static*/ class ShowMenu: public Action {
  public:
    ShowMenu(Menu*);
    virtual ~ShowMenu();
    virtual void execute();

  private:
    Menu* m_;
};

ScenePicker* Scene::picker() {
    if (!picker_) {
        WidgetKit& k = *WidgetKit::instance();
        picker_ = new ScenePicker(this);

        SceneZoom* z = new SceneZoom();
        SceneMover* m = new SceneMover();

        Menu* men = k.pulldown();
        MenuItem* mi = K::menu_item("View . . .");
        mi->menu(men);
        picker_->add_menu(mi);

        picker_->add_menu("View = plot", new WholePlotView(), men);
        picker_->add_menu("Set View", new SpecView(), men);
        picker_->add_menu("10% Zoom out", new ZoomOut10(), men);
        picker_->add_menu("10% Zoom in", new ZoomIn10(), men);
        picker_->add_radio_menu("NewView", new RubberRect(new NewView()), 0, 0, men);
        picker_->add_radio_menu("Zoom in/out", z, 0, men);
        picker_->add_radio_menu("Translate", m, 0, men);
        picker_->add_menu("Round View", new RoundView(), men);
        picker_->add_menu("Whole Scene", new WholeSceneView(), men);
        picker_->add_menu("Scene=View", new SizeScene(), men);
        picker_->add_menu("Object Name", new SPObjectName(), men);
        //		picker_->add_menu("Show Menu", new ShowMenu(men), men);
        //		picker_->add_menu(k.menu_item("Undo"));
        //		picker_->add_menu(k.menu_item("Redo"));
        picker_->add_menu(k.menu_item_separator());
        picker_->bind_select(z);
        picker_->bind_adjust(m);
    }
    return picker_;
}

/*static*/ class ScenePickerImpl: public OcHandler {
  public:
    ScenePickerImpl(Scene*);
    virtual ~ScenePickerImpl();
    virtual bool event(Event&);
    long info_index(const char*);

  public:
    friend class ScenePicker;
    PopupMenu* menu_;
    Scene* scene_;
    TelltaleGroup* tg_;
    CopyString sel_name_;
    ButtonItemInfoList* bil_;
    static DismissableWindow* window_;
};

void ScenePicker::pick_menu(Glyph* glyph, int depth, Hit& h) {
    h.target(depth, glyph, 0, spi_);
}

DismissableWindow* ScenePickerImpl::window_;

/*static*/ class RadioSelect: public Action {
  public:
    RadioSelect(const char*, Action*, Scene*);
    virtual ~RadioSelect();
    virtual void execute();

  private:
    Action* a_;
    CopyString name_;
    Scene* s_;
};

/*static*/ class RubberTool: public Action {
  public:
    RubberTool(Action* sel, Rubberband*, ScenePicker*, int);
    virtual ~RubberTool();
    virtual void execute();

  private:
    Action* sel_;
    Rubberband* rb_;
    ScenePicker* sp_;
    int tool_;
};

/*static*/ class HandlerTool: public Action {
  public:
    HandlerTool(OcHandler*, ScenePicker*, int);
    virtual ~HandlerTool();
    virtual void execute();

  private:
    OcHandler* h_;
    ScenePicker* sp_;
    int tool_;
};

ScenePicker::ScenePicker(Scene* scene) {
    spi_ = new ScenePickerImpl(scene);
    spi_->ref();
    bind_menu(spi_);
}

DismissableWindow* ScenePicker::last_window() {
    return ScenePickerImpl::window_;
}

ScenePicker::~ScenePicker() {
    spi_->unref();
}


TelltaleGroup* ScenePicker::telltale_group() {
    return spi_->tg_;
}

MenuItem* ScenePicker::add_menu(MenuItem* mi, Menu* m) {
    if (m) {
        m->append_item(mi);
    } else {
        spi_->menu_->append_item(mi);
    }
    return mi;
}

MenuItem* ScenePicker::add_menu(const char* name, MenuItem* mi, Menu* m) {
    Menu* mm;
    if (m) {
        mm = m;
    } else {
        mm = spi_->menu_->menu();
    }
    mm->append_item(mi);
    spi_->bil_->append(new ButtonItemInfo(name, mi->action(), mi->state(), mi, mm));
    return mi;
}

MenuItem* ScenePicker::add_menu(const char* name, Action* a, Menu* m) {
    MenuItem* mi = K::menu_item(name);
    mi->action(a);
    return add_menu(name, mi, m);
}
MenuItem* ScenePicker::add_radio_menu(const char* name, Action* a, Menu* m) {
    MenuItem* mi = K::radio_menu_item(spi_->tg_, name);
    mi->action(new RadioSelect(name, a, spi_->scene_));
    return add_menu(name, mi, m);
}
Button* ScenePicker::radio_button(const char* name, Action* a) {
    Button* mi = WidgetKit::instance()->radio_button(spi_->tg_,
                                                     name,
                                                     new RadioSelect(name, a, spi_->scene_));
    spi_->bil_->append(new ButtonItemInfo(name, mi->action(), mi->state()));
    return mi;
}
MenuItem* ScenePicker::add_radio_menu(const char* name,
                                      Rubberband* rb,
                                      Action* sel,
                                      int tool,
                                      Menu* m) {
    return add_radio_menu(name, new RubberTool(sel, rb, this, tool), m);
}
Button* ScenePicker::radio_button(const char* name, Rubberband* rb, Action* sel, int tool) {
    return radio_button(name, new RubberTool(sel, rb, this, tool));
}
MenuItem* ScenePicker::add_radio_menu(const char* name, OcHandler* h, int tool, Menu* m) {
    return add_radio_menu(name, new HandlerTool(h, this, tool), m);
}

long ScenePickerImpl::info_index(const char* name) {
    long i, cnt;
    cnt = bil_->count();
    for (i = 0; i < cnt; ++i) {
        ButtonItemInfo* b = bil_->item(i);
        if (strcmp(b->name_.string(), name) == 0) {
            return i;
        }
    }
    return -1;
}
void ScenePicker::exec_item(const char* name) {
    long i;
    Scene* s = spi_->scene_;
    if (s->view_count()) {
        XYView* v = s->sceneview(0);
        XYView::current_pick_view(v);
        if (v->canvas()) {
            ScenePickerImpl::window_ = (DismissableWindow*) v->canvas()->window();
        }
    } else {
        XYView::current_pick_view(NULL);
        ScenePickerImpl::window_ = NULL;
    }
    i = spi_->info_index(name);
    if (i > -1) {
        ButtonItemInfo* b = spi_->bil_->item(i);
        TelltaleState* t = b->s_;
        bool chosen = t->test(TelltaleState::is_chosen);
        bool act = !chosen;
        if (t->test(TelltaleState::is_toggle)) {
            t->set(TelltaleState::is_chosen, act);
            act = true;
        } else if (t->test(TelltaleState::is_choosable)) {
            t->set(TelltaleState::is_chosen, true);
        }
        t->notify();
        if (act && b->a_ != NULL) {
            b->a_->execute();
        }
    }
}

void ScenePicker::remove_item(const char* name) {
    long i;
    i = spi_->info_index(name);
    if (i > -1) {
        ButtonItemInfo* b = spi_->bil_->item(i);
        spi_->bil_->remove(i);
        GlyphIndex j = b->menu_index();
        if (j > -1) {
            b->parent_->remove_item(j);
        }
        //		if (b->mi_) {
        //			delete b->mi_;
        //		}
        delete b;
    }
}

void ScenePicker::insert_item(const char* insert, const char* name, MenuItem* mi) {
    long i;
    i = spi_->info_index(insert);
    if (i > -1) {
        ButtonItemInfo* b = spi_->bil_->item(i);
        GlyphIndex j = b->menu_index();
        if (j > -1) {
            b->parent_->insert_item(j, mi);
            spi_->bil_->insert(i,
                               new ButtonItemInfo(name, mi->action(), mi->state(), mi, b->parent_));
        }
    }
}

void ScenePicker::set_scene_tool(int t) {
    spi_->scene_->tool(t);
}

void ScenePicker::help() {
    spi_->scene_->help();
}

void ScenePicker::select_name(const char* name) {
    spi_->sel_name_ = name;
}

const char* ScenePicker::select_name() {
    return spi_->sel_name_.string();
}

ScenePickerImpl::ScenePickerImpl(Scene* scene)
    : sel_name_("") {
    menu_ = new PopupMenu();
    menu_->ref();
    tg_ = new TelltaleGroup();
    tg_->ref();
    scene_ = scene;  // not ref'ed since picker deleted when scene is deleted
    bil_ = new ButtonItemInfoList(20);
}

ScenePickerImpl::~ScenePickerImpl() {
    Resource::unref(menu_);
    Resource::unref(tg_);
    for (long i = bil_->count() - 1; i >= 0; --i) {
        delete bil_->item(i);
    }
    delete bil_;
}

bool ScenePickerImpl::event(Event& e) {
    // printf("ScenePickerImpl::event\n");
    window_ = (DismissableWindow*) e.window();
    menu_->event(e);
    return false;
}

RubberTool::RubberTool(Action* sel, Rubberband* rb, ScenePicker* sp, int tool) {
    sel_ = sel;
    sp_ = sp;
    rb_ = rb;
    Resource::ref(rb_);
    Resource::ref(sel_);
    tool_ = tool;
}

RubberTool::~RubberTool() {
    Resource::unref(rb_);
    Resource::unref(sel_);
}

void RubberTool::execute() {
    sp_->bind_select(rb_);
    sp_->set_scene_tool(tool_);
    if (Oc::helpmode()) {
        rb_->help();
    } else if (sel_) {
        sel_->execute();
    }
}

RadioSelect::RadioSelect(const char* name, Action* a, Scene* s)
    : name_(name) {
    a_ = a;
    Resource::ref(a_);
    s_ = s;  // not referenced
}

RadioSelect::~RadioSelect() {
    Resource::unref(a_);
}

void RadioSelect::execute() {
    handle_old_focus();
    a_->execute();
    s_->picker()->select_name(this->name_.string());
    for (int i = 0; i < s_->view_count(); ++i) {
        XYView* v = s_->sceneview(i);
        v->notify();
    }
}

HandlerTool::HandlerTool(OcHandler* h, ScenePicker* sp, int tool) {
    sp_ = sp;
    h_ = h;
    Resource::ref(h_);
    tool_ = tool;
}

HandlerTool::~HandlerTool() {
    Resource::unref(h_);
}

void HandlerTool::execute() {
    sp_->bind_select(h_);
    sp_->set_scene_tool(tool_);
    if (Oc::helpmode()) {
        if (h_) {
            h_->help();
        } else {
            sp_->help();
        }
    }
}

SceneMover::SceneMover() {
    x_ = 0;
    y_ = 0;
}

SceneMover::~SceneMover() {}

void SceneMover::help() {
    Oc::help(Scene_SceneMover_);
}

bool SceneMover::event(Event& e) {
    if (Oc::helpmode()) {
        if (e.type() == Event::down) {
            help();
        }
    }
    Coord xold = x_, yold = y_;
    x_ = e.pointer_x();
    y_ = e.pointer_y();

    switch (e.type()) {
    case Event::down:
        view_ = XYView::current_pick_view();
        //		view_->rebind();
        e.grab(this);
#ifdef WIN32
        e.window()->grab_pointer();
#endif
        break;
    case Event::motion:
        view_->move_view(x_ - xold, y_ - yold);
        break;
    case Event::up:
        e.ungrab(this);
#ifdef WIN32
        e.window()->ungrab_pointer();
#endif
        break;
    }
    return true;
}

SceneZoom::SceneZoom() {
    x_ = 0;
    y_ = 0;
    xorg_ = 0;
    yorg_ = 0;
}

SceneZoom::~SceneZoom() {}

void SceneZoom::help() {
    Oc::help(Scene_SceneZoom_);
}

bool SceneZoom::event(Event& e) {
    if (Oc::helpmode()) {
        if (e.type() == Event::down) {
            help();
        }
    }
    Coord xold = x_, yold = y_;
    x_ = e.pointer_x();
    y_ = e.pointer_y();

    switch (e.type()) {
    case Event::down:
        view_ = XYView::current_pick_view();
        //		view_->rebind();
        e.grab(this);
#ifdef WIN32
        e.window()->grab_pointer();
#endif
        xorg_ = x_;
        yorg_ = y_;
        break;
    case Event::motion:
        xold = (x_ - xold) / 50;
        yold = (y_ - yold) / 50;
        if (xold > .5)
            xold = .5;
        if (yold > .5)
            yold = .5;
        if (xold < -.5)
            xold = -.5;
        if (yold < -.5)
            yold = -.5;
        view_->scale_view(xorg_, yorg_, xold, yold);
        break;
    case Event::up:
        e.ungrab(this);
#ifdef WIN32
        e.window()->ungrab_pointer();
#endif
        break;
    }
    return true;
}

void RoundView::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_RoundView_);
        return;
    }
    XYView* v = XYView::current_pick_view();
    if (!v)
        return;
    Coord x1, y1, x2, y2;
    v->zin(x1, y1, x2, y2);
    double d1, d2;
    int ntic;
    MyMath::round_range_down(x1, x2, d1, d2, ntic);
    x1 = d1;
    x2 = d2;
    MyMath::round_range_down(y1, y2, d1, d2, ntic);
    y1 = d1;
    y2 = d2;

    v->size(x1, y1, x2, y2);
    v->zout(x1, y1, x2, y2);
    v->size(x1, y1, x2, y2);
    v->damage_all();
}

void WholeSceneView::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_WholeSceneView_);
        return;
    }
    XYView* v = XYView::current_pick_view();
    if (!v)
        return;
    Scene& s = *v->scene();
    v->size(s.x1(), s.y1(), s.x2(), s.y2());
    Coord x1, y1, x2, y2;
    v->zout(x1, y1, x2, y2);
    v->size(x1, y1, x2, y2);
    v->damage_all();
}

void WholePlotView::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_WholePlotView_);
        return;
    }
    XYView* v = XYView::current_pick_view();
    if (!v)
        return;
    Scene& s = *v->scene();
    Coord x1, y1, x2, y2;
    s.wholeplot(x1, y1, x2, y2);
    MyMath::round(x1, x2, MyMath::Expand, 2);
    MyMath::round(y1, y2, MyMath::Expand, 2);
    v->box_size(x1, y1, x2, y2);
    v->zout(x1, y1, x2, y2);
    v->box_size(x1, y1, x2, y2);
    v->damage_all();
}

void ZoomOut10::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_ZoomOut10_);
        return;
    }
    XYView* v = XYView::current_pick_view();
    Coord x1, x2, y1, y2;
    v->zout(x1, y1, x2, y2);
    v->size(x1, y1, x2, y2);
    v->damage_all();
}

void ZoomIn10::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_ZoomeIn10_);
        return;
    }
    XYView* v = XYView::current_pick_view();
    if (!v)
        return;
    Coord x1, x2, y1, y2;
    v->zin(x1, y1, x2, y2);
    v->size(x1, y1, x2, y2);
    v->damage_all();
}

void SPObjectName::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_ObjectName_);
    }
    Scene* s = XYView::current_pick_view()->scene();
    printf("%s\n", hoc_object_name(s->hoc_obj_ptr()));
}

void SpecView::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_SpecView_);
    }
    XYView* v = XYView::current_pick_view();
    if (!v)
        return;
    Coord x1, x2, y1, y2;
    v->zin(x1, y1, x2, y2);
    bool bx = var_pair_chooser("X size", x1, x2, v->canvas()->window());
    bool by = var_pair_chooser("Y size", y1, y2, v->canvas()->window());
    v->size(x1, y1, x2, y2);
    v->zout(x1, y1, x2, y2);
    v->size(x1, y1, x2, y2);
    v->damage_all();
}

void SizeScene::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_SizeScene_);
        return;
    }
    XYView* v = XYView::current_pick_view();
    if (!v)
        return;
    Coord x1, x2, y1, y2;
    v->zin(x1, y1, x2, y2);
    v->scene()->new_size(x1, y1, x2, y2);
    v->zout(x1, y1, x2, y2);
    v->size(x1, y1, x2, y2);
}

NewView::NewView() {}
NewView::~NewView() {}
void NewView::help() {
    Oc::help(Scene_NewView_);
}

void NewView::execute(Rubberband* rb) {
    if (Oc::helpmode()) {
        help();
        return;
    }
    const Event& e = rb->event();
    XYView* cv = XYView::current_pick_view();
    Coord l, b, r, t;
    Coord x1, y1, x2, y2;
    ((RubberRect*) rb)->get_rect_canvas(l, b, r, t);
    ((RubberRect*) rb)->get_rect(x1, y1, x2, y2);
    XYView* v = cv->new_view(x1, y1, x2, y2);
    ViewWindow* w = new ViewWindow(v, ((PrintableWindow*) (cv->canvas()->window()))->type());
    w->place(l + e.pointer_root_x() - e.pointer_x(), b + e.pointer_root_y() - e.pointer_y());
    w->map();
}

ShowMenu::ShowMenu(Menu* m) {
    m_ = m;
    m->ref();
}
ShowMenu::~ShowMenu() {
    Resource::unref(m_);
}
void ShowMenu::execute() {
    if (Oc::helpmode()) {
        Oc::help(Scene_ShowMenu_);
        return;
    }
    XYView* v = XYView::current_pick_view();
    v->parent()->viewmenu(m_);
}

// following doesn't work correctly yet
void OcViewGlyph::viewmenu(Glyph* m) {
    printf("OcViewGlyph::viewmenu()\n");
    if (!g_) {
        g_ = body();
        Resource::ref(g_);
        LayoutKit& lk = *LayoutKit::instance();
        WidgetKit& wk = *WidgetKit::instance();
        PolyGlyph* hbox = lk.hbox(2);
        hbox->append(lk.center(m, 0, 1));
        hbox->append(lk.center(view(), 0, 1));
        body(hbox);
        printf("add menu\n");
    } else {
        printf("delete menu\n");
        body(g_);
        Resource::unref(g_);
        g_ = NULL;
    }
}

PopupMenu::PopupMenu() {
    menu_ = WidgetKit::instance()->pulldown();
    menu_->ref();
    w_ = NULL;
    grabbed_ = false;
}

PopupMenu::~PopupMenu() {
    Resource::unref(menu_);
    if (w_) {
        delete w_;
    }
}

bool PopupMenu::event(Event& e) {
    if (!w_) {
        w_ = new PopupWindow(menu_);
#if MAC
        w_->place(10000, 10000);
        w_->map();
        w_->unmap();
#endif
    }
    switch (e.type()) {
    case Event::down:
        if (!grabbed_) {
            Coord l, b;
            w_->place(e.pointer_root_x(), e.pointer_root_y());
            w_->align(.8, .9);
#if defined(WIN32) || MAC
            l = w_->left();
            b = w_->bottom();
            if (b < 0. || l < 0.) {
                w_->align(0., 0.);
                w_->place((l > 0.) ? l : 1., (b > 0.) ? b : 20.);
            }
            w_->map();
#else
            w_->map();
            l = w_->left();
            b = w_->bottom();
            if (b < 0. || l < 0.) {
                w_->unmap();
                w_->align(0., 0.);
                w_->place((l > 0.) ? l : 1., (b > 0.) ? b : 20.);
                w_->map();
            }
#endif
            e.grab(this);
            grabbed_ = true;
            menu_->press(e);
        }
        break;
    case Event::motion:
        if (grabbed_) {
            menu_->drag(e);
        }
        break;
    case Event::up:
        if (grabbed_) {
            e.ungrab(this);
            grabbed_ = false;
            w_->unmap();
            menu_->release(e);
        }
        break;
    }
    return true;
}

void PopupMenu::append_item(MenuItem* mi) {
    menu_->append_item(mi);
}
#endif
