//#####################################################################
// Copyright 2020, Craig Schroeder.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################

#include <Core/Data_Structures/HASHTABLE.h>
#include <Core/Parsing/PARSE_ARGS.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include "components/QT_COMPONENT.h"
#include "components/QT_GRID.h"
#include "QT_CAMERA.h"
#include "QT_MAIN_WINDOW.h"
#include "QT_PICK.h"
#include "QT_RENDER_UTILS.h"
#include "QT_SLICE_MODE.h"
#include "QT_WORLD.h"
#include <fstream>
#include <functional>
#include <QApplication>
#include <QComboBox>
#include <QDialog>
#include <QGridLayout>
#include <QIntValidator>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QMenuBar>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFramebufferObjectFormat>
#include <QPainter>
#include <QPushButton>
#include <QTimer>
#include <QWindow>

namespace PhysBAM{
#define SET(name,value) (world.opt.name.Set(value),world.raw_opt[0].name=world.opt.name)
#define GET(name) world.opt.name.Value()
//#####################################################################
// Constructor
//#####################################################################
QT_MAIN_WINDOW::
QT_MAIN_WINDOW()
    :window(*new QT_WINDOW(*this)),
    world(*new QT_WORLD(*this)),camera(*new QT_CAMERA)
{
    setCentralWidget(&window);
    Create_Menus();
    Bind_Key([this](){Prev_Frame();},Qt::Key_Left);
    Bind_Key([this](){Next_Frame();},Qt::Key_Right);
    Bind_Key([this](){First_Frame();},Qt::Key_Up);
    Bind_Key([this](){Last_Frame();},Qt::Key_Down);
}
//#####################################################################
// Function Set_Simulation_Directory
//#####################################################################
void QT_MAIN_WINDOW::
Set_Simulation_Directory(const std::string& dir)
{
    world.viewer_dir.output_directory=dir;
    setWindowTitle(world.viewer_dir.output_directory.c_str());

    world.Reset();
    world.First_Frame();
    View_Bounding_Box();
    world.Read_Settings();
    window.update();
    Update_Objects_Menu();
}
//#####################################################################
// Destructor
//#####################################################################
QT_MAIN_WINDOW::
~QT_MAIN_WINDOW()
{
    delete slice_mode;
    delete &camera;
    delete &world;
}
//#####################################################################
// Function Bind_Key
//#####################################################################
void QT_MAIN_WINDOW::
Bind_Key(const std::function<void()>& f,int key,bool shift,bool ctrl,bool alt)
{
    key_bindings[shift][ctrl][alt].Get_Or_Insert(key).Append(f);
}
//#####################################################################
// Function Get_Key
//#####################################################################
const ARRAY<std::function<void()> >& QT_MAIN_WINDOW::
Get_Key(int key,bool shift,bool ctrl,bool alt) const
{
    auto* a=key_bindings[shift][ctrl][alt].Get_Pointer(key);
    if(a) return *a;
    return no_bindings;
}
//#####################################################################
// Function Create_Menus
//#####################################################################
void QT_MAIN_WINDOW::
Create_Menus()
{
    menu_file = menuBar()->addMenu(tr("&File"));
    action_save_settings = menu_file->addAction(tr("&Save settings"));
    action_reload_settings = menu_file->addAction(tr("&Reload settings"));
    action_clear_settings = menu_file->addAction(tr("&Clear settings"));
    action_reset = menu_file->addAction(tr("&Reset"));
    action_quit = menu_file->addAction(tr("&Quit"));
    QObject::connect(action_save_settings,SIGNAL(triggered()),this,SLOT(Save_Settings()));
    QObject::connect(action_reload_settings,SIGNAL(triggered()),this,SLOT(Reload_Settings()));
    QObject::connect(action_clear_settings,SIGNAL(triggered()),this,SLOT(Clear_Settings()));
    QObject::connect(action_reset,SIGNAL(triggered()),this,SLOT(Reset()));
    QObject::connect(action_quit,SIGNAL(triggered()),this,SLOT(Quit()));

    menu_options = menuBar()->addMenu(tr("&Options"));
    action_next = menu_options->addAction(tr("&Next frame"));
    action_prev = menu_options->addAction(tr("&Previous frame"));
    action_first = menu_options->addAction(tr("&First frame"));
    action_last = menu_options->addAction(tr("&Last frame"));
    action_wireframe = menu_options->addAction(tr("Toggle &wireframe"));
    action_wireframe->setCheckable(true);
    action_wireframe->setChecked(false);
    action_mode_2d = menu_options->addAction(tr("Toggle &2D mode"));
    action_mode_2d->setCheckable(true);
    action_mode_2d->setChecked(camera.mode_2d);
    action_reset_view = menu_options->addAction(tr("&Reset view"));
    action_inc_level = menu_options->addAction(tr("&Increase substeps"));
    action_dec_level = menu_options->addAction(tr("&Decrease substeps"));
    action_inc_point_size = menu_options->addAction(tr("&Increase point size"));
    action_dec_point_size = menu_options->addAction(tr("&Decrease point size"));
    action_view_bounding_box = menu_options->addAction(tr("View &bounding box"));
    action_toggle_loop = menu_options->addAction(tr("Toggle play loop"));
    action_toggle_loop->setCheckable(true);
    action_toggle_loop->setChecked(enable_loop);
    action_show_all = menu_options->addAction(tr("&Show_All"));
    action_hide_all = menu_options->addAction(tr("&Hide_All"));
    action_goto_frame = menu_options->addAction(tr("&Go to frame"));
    action_capture_frames = menu_options->addAction(tr("&Capture frames"));
    menu_slice_mode = menu_options->addMenu(tr("&Slice mode"));
    action_save_select_buffer = menu_options->addAction(tr("&Save select buffer"));
    action_save_select_buffer->setCheckable(true);
    action_save_select_buffer->setChecked(false);
    action_render_pick_buffer = menu_options->addAction(tr("&Render pick buffer"));
    action_render_pick_buffer->setCheckable(true);
    action_render_pick_buffer->setChecked(false);
    QObject::connect(action_next,SIGNAL(triggered()),this,SLOT(Next_Frame()));
    QObject::connect(action_prev,SIGNAL(triggered()),this,SLOT(Prev_Frame()));
    QObject::connect(action_first,SIGNAL(triggered()),this,SLOT(First_Frame()));
    QObject::connect(action_last,SIGNAL(triggered()),this,SLOT(Last_Frame()));
    QObject::connect(action_wireframe,SIGNAL(triggered()),this,SLOT(Toggle_Wireframe()));
    QObject::connect(action_mode_2d,SIGNAL(triggered()),this,SLOT(Toggle_2D_Mode()));
    QObject::connect(action_reset_view,SIGNAL(triggered()),this,SLOT(Reset_View()));
    QObject::connect(action_view_bounding_box,SIGNAL(triggered()),this,SLOT(View_Bounding_Box()));
    QObject::connect(action_toggle_loop,&QAction::triggered,this,[this](){enable_loop=!enable_loop;action_toggle_loop->setChecked(enable_loop);});
    QObject::connect(action_inc_level,SIGNAL(triggered()),this,SLOT(Increase_Substeps_Level()));
    QObject::connect(action_dec_level,SIGNAL(triggered()),this,SLOT(Decrease_Substeps_Level()));
    QObject::connect(action_inc_point_size,SIGNAL(triggered()),this,SLOT(Increase_Point_Size()));
    QObject::connect(action_dec_point_size,SIGNAL(triggered()),this,SLOT(Decrease_Point_Size()));
    QObject::connect(action_show_all,SIGNAL(triggered()),this,SLOT(Show_All()));
    QObject::connect(action_hide_all,SIGNAL(triggered()),this,SLOT(Hide_All()));
    QObject::connect(action_goto_frame,SIGNAL(triggered()),this,SLOT(Go_To_Frame()));
    QObject::connect(action_capture_frames,SIGNAL(triggered()),this,SLOT(Capture_Frames()));
    QObject::connect(action_save_select_buffer,SIGNAL(triggered()),this,SLOT(Toggle_Save_Select_Buffer()));
    QObject::connect(action_render_pick_buffer,SIGNAL(triggered()),this,SLOT(Toggle_Render_Pick_Buffer()));

    // menu_types = menuBar()->addMenu(tr("&Types"));
    // action_triangulated_surface = menu_types->addAction(tr("&triangulated surface"));
    // action_tetrahedralized_volume = menu_types->addAction(tr("tetrahedralized &volume"));
    // action_debug_particles = menu_types->addAction(tr("de&bug_particles"));
    // action_deformable_body_collection = menu_types->addAction(tr("&deformable_body_collection"));
    // action_face_scalar_field = menu_types->addAction(tr("&face_scalar_field"));
    // action_grid = menu_types->addAction(tr("&grid"));
    // action_grid_based_vector_field = menu_types->addAction(tr("gr&id_based_vector_field"));
    // action_levelset = menu_types->addAction(tr("&levelset"));
    // action_mac_velocity_field = menu_types->addAction(tr("ma&c_velocity_field"));
    // action_mpm_particles = menu_types->addAction(tr("&mpm_particles"));
    // action_particles = menu_types->addAction(tr("&particles"));
    // action_rigid_body_collection = menu_types->addAction(tr("&rigid_body_collection"));
    // action_scalar_field = menu_types->addAction(tr("&scalar_field"));
    // action_triangulated_area = menu_types->addAction(tr("triangulated_&area"));

    menu_objects = menuBar()->addMenu(tr("&Objects"));

    action_scale_down = menuBar()->addAction(tr("&<"));
    action_scale_reset = menuBar()->addAction(tr("&="));
    action_scale_up = menuBar()->addAction(tr("&>"));
    QObject::connect(action_scale_up,SIGNAL(triggered()),this,SLOT(Scale_Up()));
    QObject::connect(action_scale_reset,SIGNAL(triggered()),this,SLOT(Scale_Reset()));
    QObject::connect(action_scale_down,SIGNAL(triggered()),this,SLOT(Scale_Down()));

    action_play = menuBar()->addAction(tr("&Play"));

    menu_selection = menuBar()->addMenu(tr("&Selection"));
    action_draw = menu_selection->addAction(tr("&Toggle draw"));
    action_options = menu_selection->addAction(tr("&Options"));
    action_dump_info = menu_selection->addAction(tr("&Dump selection info"));
    QObject::connect(action_play,SIGNAL(triggered()),this,SLOT(Toggle_Play()));
    QObject::connect(action_draw,SIGNAL(triggered()),this,SLOT(Toggle_Draw()));
    QObject::connect(action_options,SIGNAL(triggered()),this,SLOT(Options_Selected()));
    QObject::connect(action_dump_info,SIGNAL(triggered()),&window,SLOT(Dump_Selection_Info()));
    Update_Selected();
}
//#####################################################################
// Function Update_Objects_Menu
//#####################################################################
void QT_MAIN_WINDOW::
Update_Objects_Menu()
{
    menu_objects->clear();
    menu_slice_mode->clear();
    // todo: delete object_action_list?
    for(const auto& l:world.objects)
        for(const auto& o:l)
        {
            auto obj=o.second;
            if(obj->parent) continue;
            obj->Add_To_Menu(*this, menu_objects);
            if(auto grid=dynamic_cast<QT_GRID<3>*>(&*obj))
            {
                auto action=menu_slice_mode->addAction(QString::fromStdString(o.first));
                QObject::connect(action,&QAction::triggered,this,[this,s=o.first,g=&grid->Grid()](){Slice_Mode(s,*g);});
            }
        }
}
//#####################################################################
// Function Update_Selected
//#####################################################################
void QT_MAIN_WINDOW::
Update_Selected()
{
    menu_selection->setDisabled(!world.selected);
    action_dump_info->setDisabled(!world.selected);
    action_draw->setDisabled(!world.selected);
    action_options->setDisabled(!world.selected);
}
//#####################################################################
// Function Show_All
//#####################################################################
void QT_MAIN_WINDOW::
Show_All()
{
    for(const auto& l:world.objects)
        for(const auto& o:l)
            o.second->Set_Draw(true);
    window.update();
}
//#####################################################################
// Function Hide_All
//#####################################################################
void QT_MAIN_WINDOW::
Hide_All()
{
    for(const auto& l:world.objects)
        for(const auto& o:l)
            o.second->Set_Draw(false);
    window.update();
}
//#####################################################################
// Function event
//#####################################################################
bool QT_MAIN_WINDOW::
event(QEvent *event)
{
    switch(event->type())
    {
        case QEvent::KeyPress:
            {
                auto e=dynamic_cast<QKeyEvent*>(event);
                auto m=e->modifiers();
                const auto& a=Get_Key(e->key(),(bool)(m&Qt::ShiftModifier),
                    (bool)(m&Qt::ControlModifier),(bool)(m&Qt::AltModifier));
                if(a.Size())
                {
                    for(auto i:a) i();
                    window.update();
                }
            }
            return true;
        default:
            return QMainWindow::event(event);
    }
}
//#####################################################################
// Function Quit
//#####################################################################
void QT_MAIN_WINDOW::
Quit()
{
    QApplication::quit();
}
//#####################################################################
// Function Quit
//#####################################################################
void QT_MAIN_WINDOW::
Save_Settings()
{
    std::ofstream fout(world.viewer_dir.Common_File("settings.txt"));
    world.Save_Settings(fout);
    for(const auto& l:world.objects)
        for(const auto& o:l)
            o.second->Save_Settings(fout);
}
//#####################################################################
// Function Quit
//#####################################################################
void QT_MAIN_WINDOW::
Reload_Settings()
{
    Clear_Settings();
    world.Read_Settings();
    window.update();
}
//#####################################################################
// Function Quit
//#####################################################################
void QT_MAIN_WINDOW::
Clear_Settings()
{
    world.Clear_Settings();
    for(const auto& l:world.objects)
        for(const auto& o:l)
            o.second->Clear_Settings();
    window.update();
}
//#####################################################################
// Function Quit
//#####################################################################
void QT_MAIN_WINDOW::
Reset()
{
    world.Reset();
    window.update();
}
//#####################################################################
// Function Prev_Frame
//#####################################################################
bool QT_MAIN_WINDOW::
Prev_Frame()
{
    if(!world.Prev_Frame())
        return false;
    window.update();
    Update_Objects_Menu();
    return true;
}
//#####################################################################
// Function Next_Frame
//#####################################################################
bool QT_MAIN_WINDOW::
Next_Frame()
{
    if(!world.Next_Frame())
        return false;
    window.update();
    Update_Objects_Menu();
    return true;
}
//#####################################################################
// Function First_Frame
//#####################################################################
void QT_MAIN_WINDOW::
First_Frame()
{
    Stop_Play();
    world.First_Frame();
    window.update();
    Update_Objects_Menu();
}
//#####################################################################
// Function Last_Frame
//#####################################################################
void QT_MAIN_WINDOW::
Last_Frame()
{
    Stop_Play();
    world.Last_Frame();
    window.update();
    Update_Objects_Menu();
}
//#####################################################################
// Function Options_Dialog
//#####################################################################
void QT_MAIN_WINDOW::
Options_Dialog(QT_COMPONENT* comp)
{
    comp->Make_Dialog(window);
}
//#####################################################################
// Function Options_Selected
//#####################################################################
void QT_MAIN_WINDOW::
Options_Selected()
{
    if(world.selected)
        world.selected->Make_Dialog(window);
}
//#####################################################################
// Function Options_Selected
//#####################################################################
void QT_MAIN_WINDOW::
Toggle_Draw()
{
    if(world.selected)
    {
        world.selected->Set_Draw(!world.selected->Draw());
        Update_Objects_Menu();
        window.update();
    }
}
//#####################################################################
// Function Toggle_Wireframe
//#####################################################################
void QT_MAIN_WINDOW::
Toggle_Wireframe()
{
    window.ru->wireframe=!window.ru->wireframe;
    action_wireframe->setChecked(window.ru->wireframe);
    window.update();
}
//#####################################################################
// Function Set_2D_Mode
//#####################################################################
void QT_MAIN_WINDOW::
Set_2D_Mode(bool is_2d)
{
    SET(is_2d,is_2d);
    camera.mode_2d=is_2d;
    action_mode_2d->setChecked(is_2d);
    window.update();
}
//#####################################################################
// Function Toggle_2D_Mode
//#####################################################################
void QT_MAIN_WINDOW::
Toggle_2D_Mode()
{
    Set_2D_Mode(!GET(is_2d));
}
//#####################################################################
// Function Scale_Up
//#####################################################################
void QT_MAIN_WINDOW::
Scale_Up()
{
    float s=GET(scale)*sqrt(sqrt(2.f));
    SET(scale,s);
    if(auto ru=window.ru) ru->scale=s;
    window.update();
}
//#####################################################################
// Function Scale_Down
//#####################################################################
void QT_MAIN_WINDOW::
Scale_Down()
{
    float s=GET(scale)*sqrt(sqrt(.5f));
    SET(scale,s);
    if(auto ru=window.ru) ru->scale=s;
    window.update();
}
//#####################################################################
// Function Scale_Reset
//#####################################################################
void QT_MAIN_WINDOW::
Scale_Reset()
{
    SET(scale,1.f);
    if(auto ru=window.ru) ru->scale=1;
    window.update();
}
//#####################################################################
// Function Reset_View
//#####################################################################
void QT_MAIN_WINDOW::
Reset_View()
{
    bool is_2d=camera.mode_2d;
    camera=QT_CAMERA();
    camera.mode_2d=is_2d;
    camera.Set_Size(width(),height());
    View_Bounding_Box();
    window.update();
}
//#####################################################################
// Function View_Bounding_Box
//#####################################################################
void QT_MAIN_WINDOW::
View_Bounding_Box()
{
    auto box=world.Bounding_Box();
    if(!box.Empty()) camera.Look_At(box);
    window.update();
}
//#####################################################################
// Function Stop_Play
//#####################################################################
void QT_MAIN_WINDOW::
Stop_Play()
{
    if(timer)
    {
        timer->stop();
        delete timer;
        timer = nullptr;
    }
}
//#####################################################################
// Function Toggle_Play
//#####################################################################
void QT_MAIN_WINDOW::
Toggle_Play()
{
    if(timer) Stop_Play();
    else
    {
        timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this,
            [this]()
            {
                if(!Next_Frame() && enable_loop)
                    First_Frame();
            }
        );
        timer->start(1000/24);
    }
}
//#####################################################################
// Function Increase_Substeps_Level
//#####################################################################
void QT_MAIN_WINDOW::
Increase_Substeps_Level()
{
    SET(substeps_level,GET(substeps_level)+1);
}
//#####################################################################
// Function Decrease_Substeps_Level
//#####################################################################
void QT_MAIN_WINDOW::
Decrease_Substeps_Level()
{
    SET(substeps_level,std::max(0,GET(substeps_level)-1));
}
//#####################################################################
// Function Increase_Point_Size
//#####################################################################
void QT_MAIN_WINDOW::
Increase_Point_Size()
{
    float s=GET(point_size)*sqrt(sqrt(2.f));
    SET(point_size,s);
    if(auto ru=window.ru)
    {
        ru->point_size=s;
        ru->highlighted_point_size=s*1.25f;
    }
    window.update();
}
//#####################################################################
// Function Decrease_Point_Size
//#####################################################################
void QT_MAIN_WINDOW::
Decrease_Point_Size()
{
    float s=GET(point_size)*sqrt(sqrt(.5f));
    SET(point_size,s);
    if(auto ru=window.ru)
    {
        ru->point_size=s;
        ru->highlighted_point_size=s*1.25f;
    }
    window.update();
}
//#####################################################################
// Function Go_To_Frame
//#####################################################################
void QT_MAIN_WINDOW::
Go_To_Frame()
{
    QDialog* dialog=new QDialog;
    dialog->setWindowTitle(tr("Go to frame"));
    QGridLayout* layout = new QGridLayout(dialog);
    QLineEdit* edit=new QLineEdit(QString::fromStdString(world.viewer_dir.Frame_As_String()),this);

    layout->addWidget(new QLabel(tr("Frame"),this),0,0);
    layout->addWidget(edit,0,1);

    QPushButton *cancel = new QPushButton(tr("Cancel"),this);
//    QPushButton *apply = new QPushButton(tr("Apply"),this);
    QPushButton *accept = new QPushButton(tr("Accept"),this);
    layout->addWidget(cancel,1,0);
//    layout->addWidget(apply,1,1);
    layout->addWidget(accept,1,1);

    accept->setDisabled(true);

    connect(edit,&QLineEdit::textEdited,dialog,
        [this,edit,accept]()
        {
            ARRAY<int> stack;
            world.viewer_dir.Parse_Frame(stack, edit->text().toStdString());
            bool valid=(stack.m && world.viewer_dir.Valid_Frame(stack,true));
            accept->setDisabled(!valid);
        });

    connect(accept,&QPushButton::clicked,dialog,
        [this,edit,dialog]()
        {
            world.Set_Frame(edit->text().toStdString());
            dialog->QDialog::done(QDialog::Accepted);
        });

    connect(cancel,&QPushButton::clicked,dialog,
        [this,edit,dialog]()
        {
            dialog->QDialog::done(QDialog::Rejected);
        });

    int result=dialog->exec();
    if(result==QDialog::Accepted)
        window.update();
    // if(result!=QDialog::Accepted)
    // {
    //     func.set(opt,*copy);
    // }
}
//#####################################################################
// Function Capture_Frames
//#####################################################################
void QT_MAIN_WINDOW::
Capture_Frames()
{
    QDialog* dialog=new QDialog;
    dialog->setWindowTitle(tr("Capture frames"));
    QGridLayout* layout = new QGridLayout(dialog);

    layout->addWidget(new QLabel(tr("Start frame"),this),0,0);
    QLineEdit* start_frame=new QLineEdit("0",this);
    start_frame->setValidator(new QIntValidator(this));
    layout->addWidget(start_frame,0,1);

    ARRAY<int> last_stack;
    world.viewer_dir.Read_Last_Frame(last_stack);
    int last_frame=last_stack.m?last_stack(0):0;
    layout->addWidget(new QLabel(tr("End frame"),this),1,0);
    QLineEdit* end_frame=new QLineEdit(QString::number(last_frame),this);
    end_frame->setValidator(new QIntValidator(this));
    layout->addWidget(end_frame,1,1);

    layout->addWidget(new QLabel(tr("Base filename"),this),2,0);
    QLineEdit* base_name=new QLineEdit("frame",this);
    layout->addWidget(base_name,2,1);

    QPushButton *cancel = new QPushButton(tr("Cancel"),this);
    QPushButton *accept = new QPushButton(tr("Accept"),this);
    layout->addWidget(cancel,3,0);
    layout->addWidget(accept,3,1);

    connect(accept,&QPushButton::clicked,dialog,
        [this,dialog,base_name,start_frame,end_frame]()
        {
            window.Capture(base_name->text().toStdString(),
                start_frame->text().toInt(),end_frame->text().toInt());
            dialog->QDialog::done(QDialog::Accepted);
        });

    connect(cancel,&QPushButton::clicked,dialog,
        [this,dialog]()
        {
            dialog->QDialog::done(QDialog::Rejected);
        });

    int result=dialog->exec();
    if(result==QDialog::Accepted)
        window.update();
    // if(result!=QDialog::Accepted)
    // {
    //     func.set(opt,*copy);
    // }
}
//#####################################################################
// Function Slice_Mode
//#####################################################################
void QT_MAIN_WINDOW::
Slice_Mode(const std::string& grid_name,const GRID<TV>& grid)
{
    slice_mode=new QT_SLICE_MODE(*this,grid);
    slice_mode->show();
}
//#####################################################################
// Function Toggle_Save_Select_Buffer
//#####################################################################
void QT_MAIN_WINDOW::
Toggle_Save_Select_Buffer()
{
    window.save_select=!window.save_select;
}
//#####################################################################
// Function Toggle_Render_Pick_Buffer
//#####################################################################
void QT_MAIN_WINDOW::
Toggle_Render_Pick_Buffer()
{
    window.render_pick_buffer=!window.render_pick_buffer;
    window.update();
}
}
