/*
 * This file is part of vimix - video live mixer
 *
 * **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
 *
 * 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 <https://www.gnu.org/licenses/>.
**/


#include "IconsFontAwesome5.h"
#include "imgui.h"

#include <algorithm>
#include <cstddef>
#include <glib.h>
#include <string>
#include <sstream>
#include <iomanip>

#include <glad/glad.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/component_wise.hpp>

#include "Toolkit/BaseToolkit.h"
#include "Toolkit/ImGuiToolkit.h"

#include "View/View.h"
#include "Mixer.h"
#include "defines.h"
#include "Source/Source.h"
#include "Settings.h"
#include "Visitor/PickingVisitor.h"
#include "Visitor/DrawVisitor.h"
#include "Scene/Decorations.h"
#include "UserInterfaceManager.h"
#include "Visitor/BoundingBoxVisitor.h"
#include "ActionManager.h"
#include "MousePointer.h"
#include "Scene/Scene.h"
#include "Source/CanvasSource.h"
#include "Source/SourceList.h"
#include "Canvas.h"

#include "GeometryView.h"
#include "GeometryHandleManipulation.h"

const char* GeometryView::editor_icons[2]  = { ICON_FA_OBJECT_UNGROUP, ICON_FA_BORDER_ALL };
const char *GeometryView::editor_names[2] = {"Edit sources", " Edit canvas"};


GeometryView::GeometryView() : View(GEOMETRY)
{
    scene.root()->scale_ = glm::vec3(GEOMETRY_DEFAULT_SCALE, GEOMETRY_DEFAULT_SCALE, 1.0f);
    // read default settings
    if ( Settings::application.views[mode_].name.empty() )
        // no settings found: store application default
        saveSettings();
    else
        restoreSettings();
    Settings::application.views[mode_].name = "Geometry";

    // Geometry Scene background
    output_surface_ = new Surface;
    output_surface_->visible_ = true;
    output_surface_->shader()->color = glm::vec4(0.8f, 0.8f, 0.8f, 0.5f);
    scene.bg()->attach(output_surface_);

    output_frame_ = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
    output_frame_->color = glm::vec4( 0.f, 0.f, 0.f, 0.4f );
    scene.bg()->attach(output_frame_);

    // User interface foreground
    //
    // point to show POSITION
    overlay_position_ = new Symbol(Symbol::SQUARE_POINT);
    overlay_position_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
    scene.fg()->attach(overlay_position_);
    overlay_position_->visible_ = false;
    // cross to show the axis for POSITION
    overlay_position_cross_ = new Symbol(Symbol::GRID);
    overlay_position_cross_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
    scene.fg()->attach(overlay_position_cross_);
    overlay_position_cross_->visible_ = false;
    // 'clock' : tic marks every 10 degrees for ROTATION
    // with dark background
    overlay_rotation_clock_ = new Group;
    overlay_rotation_clock_tic_ = new Symbol(Symbol::CLOCK);
    overlay_rotation_clock_->attach(overlay_rotation_clock_tic_);
    Symbol *s = new Symbol(Symbol::CIRCLE_POINT);
    s->color = glm::vec4(0.f, 0.f, 0.f, 0.1f);
    s->scale_ = glm::vec3(28.f, 28.f, 1.f);
    s->translation_.z = -0.1;
    overlay_rotation_clock_->attach(s);
    overlay_rotation_clock_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_clock_);
    overlay_rotation_clock_->visible_ = false;
    // circle to show fixed-size  ROTATION
    overlay_rotation_clock_hand_ = new Symbol(Symbol::CLOCK_H);
    overlay_rotation_clock_hand_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_clock_hand_);
    overlay_rotation_clock_hand_->visible_ = false;
    overlay_rotation_fix_ = new Symbol(Symbol::SQUARE);
    overlay_rotation_fix_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_fix_);
    overlay_rotation_fix_->visible_ = false;
    // circle to show the center of ROTATION
    overlay_rotation_ = new Symbol(Symbol::CIRCLE);
    overlay_rotation_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_);
    overlay_rotation_->visible_ = false;
    // 'grid' : tic marks every 0.1 step for SCALING
    // with dark background
    Group *g = new Group;
    s = new Symbol(Symbol::GRID);
    s->scale_ = glm::vec3(1.655f, 1.655f, 1.f);
    g->attach(s);
    s = new Symbol(Symbol::SQUARE_POINT);
    s->color = glm::vec4(0.f, 0.f, 0.f, 0.1f);
    s->scale_ = glm::vec3(17.f, 17.f, 1.f);
    s->translation_.z = -0.1;
    g->attach(s);
    overlay_scaling_grid_ = g;
    overlay_scaling_grid_->scale_ = glm::vec3(0.3f, 0.3f, 1.f);
    scene.fg()->attach(overlay_scaling_grid_);
    overlay_scaling_grid_->visible_ = false;
    // cross in the square for proportional SCALING
    overlay_scaling_cross_ = new Symbol(Symbol::CROSS);
    overlay_scaling_cross_->scale_ = glm::vec3(0.3f, 0.3f, 1.f);
    scene.fg()->attach(overlay_scaling_cross_);
    overlay_scaling_cross_->visible_ = false;
    // square to show the center of SCALING
    overlay_scaling_ = new Symbol(Symbol::SQUARE);
    overlay_scaling_->scale_ = glm::vec3(0.3f, 0.3f, 1.f);
    scene.fg()->attach(overlay_scaling_);
    overlay_scaling_->visible_ = false;
    // frame to show the full image size for CROP
    Frame *border = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
    border->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.2f );
    overlay_crop_ = border;
    scene.fg()->attach(overlay_crop_);
    overlay_crop_->visible_ = false;

    // same to show the full image size for CROP CANVAS
    border = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
    border->color = glm::vec4( COLOR_FRAME_LIGHT, 0.3f );
    canvas_overlay_crop_ = border;
    scene.fg()->attach(canvas_overlay_crop_);
    canvas_overlay_crop_->visible_ = false;
    // attach canvases to Geometry Views workspace
    canvas_scene_ = new Group;
    scene.ws()->attach( canvas_scene_ );
    // no current canvas
    current_canvas_ = nullptr;

    // will be init later
    overlay_selection_scale_ = nullptr;
    overlay_selection_rotate_ = nullptr;
    overlay_selection_stored_status_ = nullptr;
    overlay_selection_active_ = false;

    // replace grid with appropriate one
    translation_grid_ = new TranslationGrid(scene.root());
    translation_grid_->root()->visible_ = false;
    rotation_grid_ = new RotationGrid(scene.root());
    rotation_grid_->root()->visible_ = false;
    if (grid) delete grid;
    grid = translation_grid_;

}

void GeometryView::attach(Source *canvas)
{
    // attach to scene 
    canvas_scene_->attach( canvas->groups_[View::GEOMETRY] );
    canvas_scene_->attach( canvas->frames_[View::GEOMETRY] );
}

void GeometryView::detach(Source *canvas)
{
    // detach from scene
    canvas_scene_->detach( canvas->groups_[View::GEOMETRY] );
    canvas_scene_->detach( canvas->frames_[View::GEOMETRY] );

}

void GeometryView::update(float dt)
{
    View::update(dt);

    // a more complete update is requested
    if (View::need_deep_update_ > 0) {

        // update rendering of render frame
        FrameBuffer *output = Mixer::manager().session()->frame();
        if (output){
            float aspect_ratio = output->aspectRatio();
            for (NodeSet::iterator node = scene.bg()->begin(); node != scene.bg()->end(); ++node) {
                (*node)->scale_.x = aspect_ratio;
            }
            for (NodeSet::iterator node = scene.fg()->begin(); node != scene.fg()->end(); ++node) {
                (*node)->scale_.x = aspect_ratio;
            }
            output_surface_->setTextureIndex( output->texture() );

            // set grid aspect ratio
            if (Settings::application.proportional_grid)
                translation_grid_->setAspectRatio( output->aspectRatio() );
            else
                translation_grid_->setAspectRatio( 1.f );
        }

        // prevent invalid scaling
        float s = CLAMP(scene.root()->scale_.x, GEOMETRY_MIN_SCALE, GEOMETRY_MAX_SCALE);
        scene.root()->scale_.x = s;
        scene.root()->scale_.y = s;

        // change grid color
        const ImVec4 c = ImGuiToolkit::HighlightColor();
        translation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) );
        rotation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) );
    }

    // the current view is the geometry view
    if (Mixer::manager().view() == this )
    {
        ImVec4 c = ImGuiToolkit::HighlightColor();
        updateSelectionOverlay(glm::vec4(c.x, c.y, c.z, c.w));

//        overlay_selection_icon_->visible_ = false;
    }
}

void GeometryView::resize ( int scale )
{
    float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
    z *= z;
    z *= GEOMETRY_MAX_SCALE - GEOMETRY_MIN_SCALE;
    z += GEOMETRY_MIN_SCALE;

    // centered zoom : if zooming, adjust translation to ratio of scaling
    if (scale != size())
        scene.root()->translation_ *= z / scene.root()->scale_.x;

    // apply new scale
    scene.root()->scale_.x = z;
    scene.root()->scale_.y = z;

    // Clamp translation to acceptable area
    glm::vec3 border(2.f * Mixer::manager().session()->frame()->aspectRatio(), 2.f, 0.f);
    border = border * scene.root()->scale_;
    scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border);
}

int  GeometryView::size ()
{
    float z = (scene.root()->scale_.x - GEOMETRY_MIN_SCALE) / (GEOMETRY_MAX_SCALE - GEOMETRY_MIN_SCALE);
    return (int) ( sqrt(z) * 100.f);
}

void GeometryView::draw()
{
    // Drawing of Geometry view is different as it renders
    // only sources in the current workspace
    std::vector<Node *> source_surfaces;
    std::vector<Node *> source_overlays;
    std::vector<Node *> canvas_surfaces;
    std::vector<Node *> canvas_overlays;
    uint workspaces_counts_[Source::WORKSPACE_ANY+1] = {0};
    uint hidden_count_ = 0;

    // work on the current source
    Source *s = Mixer::manager().currentSource();

    // hack to prevent source manipulation (scale and rotate)
    // when multiple sources are selected: temporarily change mode to 'selected'
    if (s != nullptr &&  Mixer::selection().size() > 1)
        s->setMode(Source::SELECTED);

    for (auto source_iter = Mixer::manager().session()->begin();
            source_iter != Mixer::manager().session()->end(); ++source_iter) {
        // count if it is visible
        if (Settings::application.views[mode_].ignore_mix || (*source_iter)->visible()) {
            // if it is in the current workspace
            if (Settings::application.current_workspace == Source::WORKSPACE_ANY
                || (*source_iter)->workspace() == Settings::application.current_workspace) {
                // will draw its surface
                source_surfaces.push_back((*source_iter)->groups_[mode_]);
                // will draw its frame and locker icon
                if (editor_mode_ == EDIT_SOURCES) {
                    source_overlays.push_back((*source_iter)->frames_[mode_]);
                    source_overlays.push_back((*source_iter)->locker_);
                }
            }
            // count number of sources per workspace
            workspaces_counts_[(*source_iter)->workspace()]++;
            workspaces_counts_[Source::WORKSPACE_ANY]++;
        }
        hidden_count_ += (*source_iter)->visible() ? 0 : 1;
    }

    // draw all canvases 
    for (auto canvas_iter = Canvas::manager().begin();
            canvas_iter != Canvas::manager().end(); ++canvas_iter) {
        // will draw its surface
        canvas_surfaces.push_back((*canvas_iter)->rendersurface_);
        // will draw its frame
        canvas_overlays.push_back((*canvas_iter)->frames_[GEOMETRY]);
    }
    
    // 0. prepare projection for draw visitors
    glm::mat4 projection = Rendering::manager().Projection();

    // 1. Draw output surface (render frame to show global framebuffer, semi transparent)
    output_surface_->shader()->color.a = editor_mode_ == EDIT_CANVAS ? 0.6f : 0.3f;
    DrawVisitor draw_rendering(scene.bg(), projection);
    scene.accept(draw_rendering);

    if (editor_mode_ != EDIT_CANVAS) {
        // 2. Draw surface of sources in the current workspace
        DrawVisitor draw_sources(source_surfaces, projection);
        scene.accept(draw_sources);
    }

    // 3. Draw canvases on top of sources 
    DrawVisitor draw_canvases(canvas_surfaces, projection);
    scene.accept(draw_canvases);

    if (!source_overlays.empty())  {
        // 5. Draw frames and icons of sources in the current workspace
        DrawVisitor draw_overlays(source_overlays, projection);
        scene.accept(draw_overlays);

        // 6. Draw control overlays of current source on top (if selected)
        if (s != nullptr &&
            (Settings::application.current_workspace == Source::WORKSPACE_ANY ||
             s->workspace() == Settings::application.current_workspace) &&
            (Settings::application.views[mode_].ignore_mix || s->visible()))
        {
            DrawVisitor dv(s->overlays_[mode_], projection);
            scene.accept(dv);
            // Always restore current source after draw
            s->setMode(Source::CURRENT);
        }
    }
    
    // 7. Draw frames of the canvases
    DrawVisitor draw_overlays(canvas_overlays, projection);
    scene.accept(draw_overlays);

    if (editor_mode_ == EDIT_CANVAS && current_canvas_ != nullptr) {

        std::vector<Node *> canvas_handles = {
            current_canvas_->handles_[mode_][Handles::CROP_H],
            current_canvas_->handles_[mode_][Handles::CROP_V],
            current_canvas_->handles_[mode_][Handles::MENU], 
            dynamic_cast<CanvasSurface *>(current_canvas_)->label
        };
        DrawVisitor dv(canvas_handles, projection);
        scene.accept(dv);
    }

    // 8. Finally, draw overlays of view
    DrawVisitor draw_foreground(scene.fg(), projection);
    scene.accept(draw_foreground);

    // 9. Display grid
    if (grid->active() && current_action_ongoing_) {
        DrawVisitor draw_grid(grid->root(), projection, true);
        scene.accept(draw_grid);
    }

    //
    // 10. Display interface
    //
    // Locate window at upper right corner
    glm::vec2 P(-output_surface_->scale_.x, output_surface_->scale_.y + 0.01f);
    P = Rendering::manager().project(glm::vec3(P, 0.f), scene.root()->transform_, false);
    // Set window position depending on icons size
    ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
    ImGui::SetNextWindowPos(ImVec2(P.x, P.y - 2.f * ImGui::GetFrameHeight() ), ImGuiCond_Always);
    if (ImGui::Begin("##GeometryViewOptions", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground
                     | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings
                     | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus ))
    {
        // style
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f));
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.15f, 0.5f));
        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.16f, 0.16f, 0.16f, 0.99f));
        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.15f, 0.15f, 0.15f, 0.99f));
        ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));

        // SELECT EDITOR MODE
        ImGui::SetNextItemWidth(ImGui::GetTextLineHeightWithSpacing() * 2.6);
        if (ImGui::Button(
                std::string(std::string(editor_icons[editor_mode_]) + " " + ICON_FA_SORT_DOWN)
                    .c_str()))
            ImGui::OpenPopup("Geometry_mode_menu_popup");
        if (ImGui::IsItemHovered())
            ImGuiToolkit::ToolTip(editor_names[editor_mode_]);
        if (ImGui::BeginPopup("Geometry_mode_menu_popup")) {
            ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
            for (int m = GeometryView::EDIT_SOURCES; m <= GeometryView::EDIT_CANVAS; ++m) {
                if (ImGui::Selectable(
                        std::string(std::string(editor_icons[m]) + " " + editor_names[m]).c_str())) {
                    if (m != editor_mode_) {
                        // Switch mode
                        editor_mode_ = m;
                        // actions to do when changing mode
                        if (editor_mode_ == GeometryView::EDIT_CANVAS){
                            // clear selection (disabled for canvases)
                            Mixer::selection().clear();
                            // select first canvas as current if not already one selected
                            setCurrentCanvas(*Canvas::manager().begin());
                        }
                        else {
                            // temporarily disable current mode of canvas
                            if (current_canvas_ != nullptr) 
                                current_canvas_->setMode(Source::VISIBLE);
                            // save status on exit
                            Canvas::manager().save();
                        }
                    }
                }
            }
            ImGui::PopFont();
            ImGui::EndPopup();
        }

        // CANVAS EDIT OPTIONS
        if (editor_mode_ == EDIT_CANVAS) {

            // - Remove canvas
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (Canvas::manager().size() > 1) {
                if (ImGui::Button(ICON_FA_MINUS )) {
                    // remove last canvas
                    Canvas::manager().removeSurface();
                    // set another canvas as current
                    setCurrentCanvas(*(--Canvas::manager().end()));
                }
                if (ImGui::IsItemHovered())
                    ImGuiToolkit::ToolTip("Remove canvas");
            }
            else{
                ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 0.5f));
                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.f, 0.f, 0.f, 0.f));
                ImGui::Button(ICON_FA_MINUS );
                ImGui::PopStyleColor(2);
            }

            // + Add more canvas
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (Canvas::manager().size() < MAX_OUTPUT_CANVAS) {
                if (ImGui::Button(ICON_FA_PLUS )) {
                    // create new canvas
                    Canvas::manager().addSurface();
                    // set newly created canvas as current
                    setCurrentCanvas(*(--Canvas::manager().end()));
                }
                if (ImGui::IsItemHovered())
                    ImGuiToolkit::ToolTip("Add canvas");
            } 
            else {
                ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 0.5f));
                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.f, 0.f, 0.f, 0.f));
                ImGui::Button(ICON_FA_MINUS );
                ImGui::PopStyleColor(2);
            }

            // layout selection
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGui::Button(ICON_FA_TH ICON_FA_SORT_DOWN ))
                ImGui::OpenPopup("combinations_popup");
            if (ImGui::IsItemHovered())
                ImGuiToolkit::ToolTip("Layout");
            if (ImGui::BeginPopup("combinations_popup", ImGuiWindowFlags_NoMove))  {

                ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
                if (Canvas::manager().size() == 1) {
                    if (ImGui::Selectable(ICON_FA_EXPAND "   Fit whole output")) 
                        Canvas::manager().setLayout(1, 0);
                }
                else {
                    // get grid combinations for current number of canvases
                    int N = Canvas::manager().size();
                    std::vector<std::pair<int, int>> combinations = BaseToolkit::getGridCombinations(N);
                    std::vector<std::string> descriptions = BaseToolkit::getGridCombinationDescriptions(N);
                    for (size_t i = 0; i < descriptions.size(); ++i) {
                        std::ostringstream oss;
                        oss << ICON_FA_TH << "   " << descriptions[i];
                        if (ImGui::Selectable(oss.str().c_str())) {
                            // // set combination
                            Canvas::manager().setLayout(combinations[i].first, 
                                combinations[i].second);
                        }
                    }
                }

                // reset option
                ImGui::Separator();
                if (ImGui::Selectable(ICON_FA_BACKSPACE "  Reset all")) {
                    Canvas::manager().reset(true, false);
                    // clear current canvas
                    setCurrentCanvas(nullptr);
                }

                // TODO : add load and save canvas layout ?
                ImGui::PopFont();
                ImGui::EndPopup();
            }

            // cancel current canvas edition
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            if (ImGui::Button(ICON_FA_TIMES_CIRCLE )) {
                // restore saved status
                Canvas::manager().load();
                // clear current canvas
                setCurrentCanvas(nullptr);
                // switch back to source edition
                editor_mode_ = EDIT_SOURCES;
            }
            if (ImGui::IsItemHovered())
                ImGuiToolkit::ToolTip("Cancel");
        }
        // SOURCES EDIT OPTIONS
        else {

            // toggle sources visibility flag
            std::string _label = Settings::application.views[mode_].ignore_mix ? "Show " : "Hide ";
            _label += "non visible sources\n(";
            _label += std::to_string(hidden_count_) + " source" + (hidden_count_>1?"s are ":" is ") + "outside mixing circle)";            
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            ImGuiToolkit::ButtonIconToggle(12, 0, &Settings::application.views[mode_].ignore_mix, _label.c_str());

            // select layers visibility
            static std::vector<std::tuple<int, int, std::string> > _workspaces
                = {{ICON_WORKSPACE_BACKGROUND, "Show only sources in\nBackground layer ("},
                   {ICON_WORKSPACE_CENTRAL,    "Show only sources in\nWorkspace layer ("},
                   {ICON_WORKSPACE_FOREGROUND, "Show only sources in\nForeground layer ("},
                   {ICON_WORKSPACE,            "Show sources in all layers ("}
                };
            ImGui::SameLine(0, IMGUI_SAME_LINE);
            std::ostringstream oss;
            oss << std::get<2>(_workspaces[Settings::application.current_workspace]);
            oss << std::to_string(workspaces_counts_[Settings::application.current_workspace]);
            oss << ")";
            if (ImGuiToolkit::ButtonIcon(std::get<0>(
                                             _workspaces[Settings::application.current_workspace]),
                                         std::get<1>(
                                             _workspaces[Settings::application.current_workspace]),
                                         oss.str().c_str() )) {
                Settings::application.current_workspace = (Settings::application.current_workspace+1)%4;
            }

        }

        ImGui::PopStyleColor(6);
        ImGui::End();
    }
    ImGui::PopFont();

    // display popup menu source
    if (show_context_menu_ == MENU_SOURCE) {
        ImGui::OpenPopup( "GeometrySourceContextMenu" );
        show_context_menu_ = MENU_NONE;
    }
    if (ImGui::BeginPopup("GeometrySourceContextMenu")) {
        if (s != nullptr) {
            // CROP source manipulation mode
            if (s->manipulator_->active() > 0) {
                if (ImGui::MenuItem(ICON_FA_VECTOR_SQUARE "  Switch to Shape mode"))
                    s->manipulator_->setActive(0);
                ImGui::Separator();

                if (ImGui::MenuItem(ICON_FA_CROP_ALT "  Reset crop")) {
                    s->group(mode_)->crop_ = glm::vec4(-1.f, 1.f, 1.f, -1.f);
                    s->touch();
                    Action::manager().store(s->name() + std::string(": Reset crop"));
                }
                ImGui::Text(ICON_FA_ANGLE_LEFT);
                ImGui::SameLine(18);
                if (ImGui::MenuItem(ICON_FA_ANGLE_RIGHT "   Reset corners")) {
                    s->group(mode_)->data_ = glm::zero<glm::mat4>();
                    s->touch();
                    Action::manager().store(s->name() + std::string(": Reset corners"));
                }
            }
            // SHAPE source manipulation mode
            else {
                if (ImGui::MenuItem( ICON_FA_CROP_ALT "  Switch to Crop mode" ))
                    s->manipulator_->setActive(1);
                ImGui::Separator();

                if (ImGui::MenuItem( ICON_FA_VECTOR_SQUARE "  Reset shape" )){
                    s->group(mode_)->scale_ = glm::vec3(1.f);
                    s->group(mode_)->rotation_.z = 0;
                    s->touch();
                    Action::manager().store(s->name() + std::string(": Reset shape"));
                }
                if (ImGui::MenuItem( ICON_FA_CIRCLE_NOTCH "  Reset rotation" )){
                    s->group(mode_)->rotation_.z = 0;
                    s->touch();
                    Action::manager().store(s->name() + std::string(": Reset rotation"));
                }
            }
            ImGui::Separator();
            if (ImGui::MenuItem( ICON_FA_CROSSHAIRS "  Center" )){
                s->group(mode_)->translation_ = glm::vec3(0.f);
                s->touch();
                Action::manager().store(s->name() + std::string(": Center"));
            }
            if (ImGui::MenuItem( ICON_FA_EXPAND_ALT "  Restore aspect ratio" )){
                s->group(mode_)->scale_.x = s->group(mode_)->scale_.y;
                s->group(mode_)->scale_.x *= (s->group(mode_)->crop_[1] - s->group(mode_)->crop_[0]) /
                                             (s->group(mode_)->crop_[2] - s->group(mode_)->crop_[3]);
                s->touch();
                Action::manager().store(s->name() + std::string(": Restore aspect ratio"));
            }

            if (ImGui::BeginMenu(ICON_FA_EXPAND "  Fit to...")) {

                if (ImGui::MenuItem(  "Mix output" )){
                    s->group(mode_)->scale_ = glm::vec3(output_surface_->scale_.x/ s->frame()->aspectRatio(), 1.f, 1.f);
                    s->group(mode_)->rotation_.z = 0;
                    s->group(mode_)->translation_ = glm::vec3(0.f);
                    s->touch();
                    Action::manager().store(s->name() + std::string(": Fit to Mix output"));
                }

                for (auto canvas_iter = Canvas::manager().begin();
                        canvas_iter != Canvas::manager().end(); ++canvas_iter) {
                    std::string label =  (*canvas_iter)->name();
                    if (ImGui::MenuItem( label.c_str() )) {
                        float canvas_aspect = (*canvas_iter)->frame()->aspectRatio() / s->frame()->aspectRatio();
                        s->group(mode_)->scale_.x = (*canvas_iter)->group(mode_)->scale_.x * canvas_aspect;
                        s->group(mode_)->scale_.y = (*canvas_iter)->group(mode_)->scale_.y;
                        s->group(mode_)->rotation_.z = 0;
                        s->group(mode_)->translation_.x = (*canvas_iter)->group(mode_)->translation_.x;
                        s->group(mode_)->translation_.y = (*canvas_iter)->group(mode_)->translation_.y;
                        s->touch();
                        Action::manager().store(s->name() + std::string(": Fit to ") + label);
                    }
                }

                ImGui::EndMenu();
            }

        }
        ImGui::EndPopup();
    }
    // display popup menu selection
    if (show_context_menu_ == MENU_SELECTION) {
        ImGui::OpenPopup( "GeometrySelectionContextMenu" );
        show_context_menu_ = MENU_NONE;
    }
    if (ImGui::BeginPopup("GeometrySelectionContextMenu")) {

        // colored context menu
        ImGui::PushStyleColor(ImGuiCol_Text, ImGuiToolkit::HighlightColor());
        ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(COLOR_MENU_HOVERED, 0.8f));

        // batch manipulation of sources in Geometry view
        if (ImGui::Selectable( ICON_FA_EXPAND "  Fit all" )){
            for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){
                (*sit)->group(mode_)->scale_ = glm::vec3(output_surface_->scale_.x/ (*sit)->frame()->aspectRatio(), 1.f, 1.f);
                (*sit)->group(mode_)->rotation_.z = 0;
                (*sit)->group(mode_)->translation_ = glm::vec3(0.f);
                (*sit)->touch();
            }
            Action::manager().store(std::string("Fit selected " ICON_FA_OBJECT_UNGROUP));
        }
        if (ImGui::Selectable( ICON_FA_VECTOR_SQUARE "  Reset all" )){
            // apply to every sources in selection
            for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){
                (*sit)->group(mode_)->scale_ = glm::vec3(1.f);
                (*sit)->group(mode_)->rotation_.z = 0;
                (*sit)->group(mode_)->crop_ = glm::vec4(-1.f, 1.f, 1.f, -1.f);
                (*sit)->group(mode_)->translation_ = glm::vec3(0.f);
                (*sit)->touch();
            }
            Action::manager().store(std::string("Reset selected " ICON_FA_OBJECT_UNGROUP));
        }
//        if (ImGui::Selectable( ICON_FA_TH "  Mosaic" )){ // TODO

//        }
        ImGui::Separator();
        // manipulation of selection
        if (ImGui::Selectable( ICON_FA_CROSSHAIRS "  Center" )){
            glm::mat4 T = glm::translate(glm::identity<glm::mat4>(), -overlay_selection_->translation_);
            initiate();
            applySelectionTransform(T);
            terminate();
            Action::manager().store(std::string("Center selected " ICON_FA_OBJECT_UNGROUP));
        }
        if (ImGui::Selectable( ICON_FA_COMPASS "  Align" )){
            // apply to every sources in selection
            for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){
                (*sit)->group(mode_)->rotation_.z = overlay_selection_->rotation_.z;
                (*sit)->touch();
            }
            Action::manager().store(std::string("Align selected " ICON_FA_OBJECT_UNGROUP));
        }
        if (ImGui::Selectable( ICON_FA_OBJECT_GROUP "  Best Fit" )){
            glm::mat4 T = glm::translate(glm::identity<glm::mat4>(), -overlay_selection_->translation_);
            float factor = 1.f;
            float angle = -overlay_selection_->rotation_.z;
            if ( overlay_selection_->scale_.x < overlay_selection_->scale_.y) {
                factor *= output_surface_->scale_.x / overlay_selection_->scale_.y;
                angle += glm::pi<float>() / 2.f;
            }
            else {
                factor *= output_surface_->scale_.x / overlay_selection_->scale_.x;
            }
            glm::mat4 S = glm::scale(glm::identity<glm::mat4>(), glm::vec3(factor, factor, 1.f));
            glm::mat4 R = glm::rotate(glm::identity<glm::mat4>(), angle, glm::vec3(0.f, 0.f, 1.f) );
            glm::mat4 M = S * R * T;
            initiate();
            applySelectionTransform(M);
            terminate();
            Action::manager().store(std::string("Best Fit selected " ICON_FA_OBJECT_UNGROUP));
        }
        if (ImGui::Selectable( ICON_FA_EXCHANGE_ALT "  Mirror" )){
            glm::mat4 T = glm::translate(glm::identity<glm::mat4>(), -overlay_selection_->translation_);
            glm::mat4 F = glm::scale(glm::identity<glm::mat4>(), glm::vec3(-1.f, 1.f, 1.f));
            glm::mat4 M = glm::inverse(T) * F * T;
            initiate();
            applySelectionTransform(M);
            terminate();
            Action::manager().store(std::string("Mirror selected " ICON_FA_OBJECT_UNGROUP));
        }

        ImGui::PopStyleColor(2);
        ImGui::EndPopup();
    }
    // display popup menu canvas
    if (show_context_menu_ == MENU_CANVAS) {
        ImGui::OpenPopup( "GeometryCanvasContextMenu" );
        show_context_menu_ = MENU_NONE;
    }
    if (ImGui::BeginPopup("GeometryCanvasContextMenu")) {
        if (current_canvas_ != nullptr) {
            
            ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(COLOR_MENU_HOVERED, 0.8f));
            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(COLOR_FRAME_LIGHT, 1.f));

            if (ImGui::MenuItem(ICON_FA_EXPAND "  Fit to output")) {
                current_canvas_->group(mode_)->translation_ = glm::vec3(0.f, 0.f, 0.f);
                current_canvas_->group(mode_)->scale_ = glm::vec3(1.f, 1.f, 1.f);
                current_canvas_->group(mode_)->crop_ = glm::vec4(-1.f, 1.f, 1.f, -1.f);
                current_canvas_->touch();;
            }
            for (auto sit = Canvas::manager().begin();
                 sit != Canvas::manager().end(); ++sit) {
                if (*sit != current_canvas_) {
                    if (ImGui::MenuItem(std::string(ICON_FA_EXCHANGE_ALT "  Swap with " + (*sit)->name()).c_str())) {
                        // swap parameters
                        glm::vec3 t = current_canvas_->group(mode_)->translation_;
                        glm::vec3 s = current_canvas_->group(mode_)->scale_;
                        glm::vec4 c = current_canvas_->group(mode_)->crop_;
                        current_canvas_->group(mode_)->translation_ = (*sit)->group(mode_)->translation_;
                        current_canvas_->group(mode_)->scale_ = (*sit)->group(mode_)->scale_;
                        current_canvas_->group(mode_)->crop_ = (*sit)->group(mode_)->crop_;
                        (*sit)->group(mode_)->translation_ = t;
                        (*sit)->group(mode_)->scale_ = s;
                        (*sit)->group(mode_)->crop_ = c;
                        current_canvas_->touch();
                        (*sit)->touch();
                    }
                }
            }   
            ImGui::PopStyleColor(2);
        }
        ImGui::EndPopup();
    }
}

void GeometryView::adaptGridToSource(Source *s, Node *picked)
{
    // Reset by default
    rotation_grid_->root()->translation_ = glm::vec3(0.f);
    rotation_grid_->root()->scale_ = glm::vec3(1.f);
    translation_grid_->root()->translation_ = glm::vec3(0.f);
    translation_grid_->root()->rotation_.z  = 0.f;

    if (s != nullptr && picked != nullptr) {
        if (picked == s->handles_[mode_][Handles::ROTATE]) {
            // shift grid at center of source
            rotation_grid_->root()->translation_ = s->group(mode_)->translation_;
            rotation_grid_->root()->scale_.x = glm::length(
                        glm::vec2(s->frame()->aspectRatio() * s->group(mode_)->scale_.x,
                                  s->group(mode_)->scale_.y) );
            rotation_grid_->root()->scale_.y = rotation_grid_->root()->scale_.x;
            // Swap grid to rotation grid
            rotation_grid_->setActive( grid->active() );
            translation_grid_->setActive( false );
            grid = rotation_grid_;
            return;
        }
        else if ( picked == s->handles_[mode_][Handles::RESIZE] ||
                  picked == s->handles_[mode_][Handles::RESIZE_V] ||
                  picked == s->handles_[mode_][Handles::RESIZE_H] ){
            translation_grid_->root()->translation_ = glm::vec3(0.f);
            translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z;
            // Swap grid to translation grid
            translation_grid_->setActive( grid->active() );
            rotation_grid_->setActive( false );
            grid = translation_grid_;
        }
        else if ( picked == s->handles_[mode_][Handles::SCALE] ||
                  picked == s->handles_[mode_][Handles::NODE_LOWER_LEFT] ||
                  picked == s->handles_[mode_][Handles::NODE_UPPER_LEFT] ||
                  picked == s->handles_[mode_][Handles::NODE_LOWER_RIGHT] ||
                  picked == s->handles_[mode_][Handles::NODE_UPPER_RIGHT] ||
                  picked == s->handles_[mode_][Handles::CROP_V] ||
                  picked == s->handles_[mode_][Handles::CROP_H] ){
            translation_grid_->root()->translation_ = s->group(mode_)->translation_;
            translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z;
            // Swap grid to translation grid
            translation_grid_->setActive( grid->active() );
            rotation_grid_->setActive( false );
            grid = translation_grid_;
        }
    }
    else {
        // Default:
        // Grid in scene global coordinate
        translation_grid_->setActive( grid->active()  );
        rotation_grid_->setActive( false );
        grid = translation_grid_;
    }
}

std::pair<Node *, glm::vec2> GeometryView::pick(glm::vec2 P)
{
    // prepare empty return value
    std::pair<Node *, glm::vec2> pick = { nullptr, glm::vec2(0.f) };

    // unproject mouse coordinate into scene coordinates
    glm::vec3 scene_point_ = Rendering::manager().unProject(P);

    // picking visitor traverses the scene
    PickingVisitor pv(scene_point_, false);
    scene.accept(pv);

    // picking visitor found nodes?
    if ( !pv.empty() ) {

        // SOURCE EDIT
        if (editor_mode_ == EDIT_SOURCES) {

            // keep current source active if it is clicked
            Source *current = Mixer::manager().currentSource();
            if (current != nullptr) {
                if ((Settings::application.current_workspace < Source::WORKSPACE_ANY &&
                     current->workspace() != Settings::application.current_workspace) ||
                    (!Settings::application.views[mode_].ignore_mix && !current->visible()) )
                {
                    current = nullptr;
                }
                else {
                    // find if the current source was picked
                    auto itp = pv.rbegin();
                    for (; itp != pv.rend(); ++itp){
                        // test if source contains this node
                        Source::hasNode is_in_source((*itp).first );
                        if ( is_in_source( current ) ){
                            // a node in the current source was clicked !
                            pick = *itp;
                            // adapt grid to prepare grab action
                            adaptGridToSource(current, pick.first);
                            break;
                        }
                    }
                    // not found: the current source was not clicked
                    // OR the selection contains multiple sources and actions on single source are disabled
                    if (itp == pv.rend() || Mixer::selection().size() > 1) {
                        current = nullptr;
                        pick = { nullptr, glm::vec2(0.f) };
                    }
                    // picking on the menu handle: show context menu
                    else if ( pick.first == current->handles_[mode_][Handles::MENU] ) {
                        openContextMenu(MENU_SOURCE);
                    }
                    // picking on the crop handle : switch to shape manipulation mode
                    else if (pick.first == current->handles_[mode_][Handles::EDIT_CROP]) {
                        current->manipulator_->setActive(0);
                        pick = { current->handles_[mode_][Handles::EDIT_SHAPE], glm::vec2(0.f) };
                    }
                    // picking on the shape handle : switch to crop manipulation mode
                    else if (pick.first == current->handles_[mode_][Handles::EDIT_SHAPE]) {
                        current->manipulator_->setActive(1);
                        pick = { current->handles_[mode_][Handles::EDIT_CROP], glm::vec2(0.f) };
                    }
                    // pick on the lock icon; unlock source
                    else if ( UserInterface::manager().ctrlModifier() && pick.first == current->lock_ ) {
                        lock(current, false);
                        pick = { current->locker_, pick.second };
                        //                    pick = { nullptr, glm::vec2(0.f) };
                    }
                    // pick on the open lock icon; lock source and cancel pick
                    else if ( UserInterface::manager().ctrlModifier() && pick.first == current->unlock_ ) {
                        lock(current, true);
                        pick = { nullptr, glm::vec2(0.f) };
                    }
                    // pick a locked source ; cancel pick
                    else if ( !UserInterface::manager().ctrlModifier() && current->locked() ) {
                        pick = { nullptr, glm::vec2(0.f) };
                    }
                }
            }
            // the clicked source changed (not the current source)
            if (current == nullptr) {

                if (UserInterface::manager().ctrlModifier()) {

                    // default to failed pick
                    pick = { nullptr, glm::vec2(0.f) };

                    // loop over all nodes picked to detect clic on locks
                    for (auto itp = pv.rbegin(); itp != pv.rend(); ++itp){
                        // get if a source was picked
                        Source *s = Mixer::manager().findSource((*itp).first);
                        // lock icon of a source (not current) is picked : unlock
                        if ( s != nullptr && s->locked() && (*itp).first == s->lock_) {
                            lock(s, false);
                            pick = { s->locker_, (*itp).second };
                            break;
                        }
                    }
                }
                // no lock icon picked, find what else was picked
                if ( pick.first == nullptr) {

                    // loop over all nodes picked
                    for (auto itp = pv.rbegin(); itp != pv.rend(); ++itp){
                        // get if a source was picked
                        Source *s = Mixer::manager().findSource((*itp).first);
                        // accept picked sources in current workspaces
                        if ( s!=nullptr &&
                            (Settings::application.current_workspace == Source::WORKSPACE_ANY ||
                             s->workspace() == Settings::application.current_workspace) &&
                            (Settings::application.views[mode_].ignore_mix || s->visible()) )
                        {
                            if ( !UserInterface::manager().ctrlModifier() ) {
                                // source is locked; can't move
                                if ( s->locked() )
                                    continue;
                                // a non-locked source is picked (anywhere)
                                // not in an active selection? don't pick this one!
                                if ( Mixer::selection().size() > 1 && !Mixer::selection().contains(s) )
                                    continue;
                            }
                            // yeah, pick this one
                            pick = { s->group(mode_),  (*itp).second };
                            break;
                        }
                        // not a source picked
                        else {
                            // picked on selection handles
                            if ( (*itp).first == overlay_selection_scale_ || (*itp).first == overlay_selection_rotate_ ) {
                                pick = (*itp);
                                // initiate selection manipulation
                                if (overlay_selection_stored_status_) {
                                    overlay_selection_stored_status_->copyTransform(overlay_selection_);
                                    overlay_selection_active_ = true;
                                }
                                break;
                            }
                            else if ( overlay_selection_icon_ != nullptr && (*itp).first == overlay_selection_icon_ ) {
                                pick = (*itp);
                                openContextMenu(MENU_SELECTION);
                                break;
                            }
                        }
                    }
                }
            }
        }
        // CANVAS EDIT
        else {
            // canvas are not sources; cancel normal source picking
            pick = { nullptr, glm::vec2(0.f) };

            // remember picked sources to cycle through them
            static SourceListUnique picked_sources;

            // keep current canvas active if it is clicked
            Source *picked_canvas = current_canvas_;
            if (picked_canvas != nullptr) {
                // find if the current canvas was picked
                auto itp = pv.rbegin();
                for (; itp != pv.rend(); ++itp){
                    // test if canvas contains this node
                    Source::hasNode is_in_source((*itp).first );
                    if ( is_in_source( picked_canvas ) ){
                        // a node in the current canvas was clicked !
                        pick = *itp;
                        break;
                    }
                }
                // not found: the current canvas was not clicked
                if (itp == pv.rend() ) {
                    picked_sources.clear();
                    picked_canvas = nullptr;
                }
                // picking on the menu handle: show context menu & reset picked sources
                else if ( pick.first == picked_canvas->handles_[mode_][Handles::MENU] ) {
                    openContextMenu(MENU_CANVAS); 
                    picked_sources.clear();
                }
                // picking on the manipulation handle: reset picked sources
                else if ( pick.first == picked_canvas->handles_[mode_][Handles::CROP_H] ||
                          pick.first == picked_canvas->handles_[mode_][Handles::CROP_V] ) 
                {
                    picked_sources.clear();
                }
                // second clic not on a handle, maybe select another canvas below
                else {
                    if (picked_sources.empty()) {
                        // loop over all nodes picked to fill the list of sources clicked
                        for (auto itp = pv.rbegin(); itp != pv.rend(); ++itp) {
                            SourceList::iterator sit = std::find_if(Canvas::manager().begin(), 
                                Canvas::manager().end(), Source::hasNode( (*itp).first ));
                            if ( sit != Canvas::manager().end() ) {
                                picked_sources.insert( *sit );
                            }
                        }
                    }
                    if (!picked_sources.empty()){
                        setCurrentCanvas( *picked_sources.begin() );
                        picked_sources.erase(picked_sources.begin());
                    }
                }
            }
            // the clicked canvas might have changed
            if (picked_canvas == nullptr) {

                // default to no canvas picked
                if(current_canvas_ != nullptr)
                    setCurrentCanvas(nullptr);

                // loop over all nodes picked
                for (auto itp = pv.rbegin(); itp != pv.rend(); ++itp){

                    // loop over all canvases to get if one was picked
                    SourceList::iterator sit = std::find_if(Canvas::manager().begin(), 
                        Canvas::manager().end(), Source::hasNode( (*itp).first ));

                    // accept picked canvas
                    if ( sit != Canvas::manager().end() ) {
                        setCurrentCanvas(*sit);
                        break;
                    }
                }
            }
        }

    }
    // nothing picked
    else if( editor_mode_ == EDIT_CANVAS ) {
        // cancel current canvas if clicked outside
        setCurrentCanvas(nullptr);
    }

    return pick;
}

bool GeometryView::canSelect(Source *s)
{
    if (editor_mode_ != EDIT_SOURCES)
        return false;

    return ( s!=nullptr && View::canSelect(s) && s->ready() &&
            (Settings::application.views[mode_].ignore_mix || s->visible()) &&
            (Settings::application.current_workspace == Source::WORKSPACE_ANY || s->workspace() == Settings::application.current_workspace) );
}


void GeometryView::applySelectionTransform(glm::mat4 M)
{
    for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){
        if ( (*sit)->locked() )
            continue;
        // recompute all from matrix transform
        glm::mat4 transform = M * (*sit)->stored_status_->transform_;
        glm::vec3 tra, rot, sca;
        GlmToolkit::inverse_transform(transform, tra, rot, sca);
        (*sit)->group(mode_)->translation_ = tra;
        (*sit)->group(mode_)->scale_ = sca;
        (*sit)->group(mode_)->rotation_ = rot;
        // will have to be updated
        (*sit)->touch();
    }
}

View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
{
    View::Cursor ret = Cursor();

    // grab coordinates in scene-View reference frame
    glm::vec3 scene_from = Rendering::manager().unProject(from, scene.root()->transform_);
    glm::vec3 scene_to   = Rendering::manager().unProject(to, scene.root()->transform_);

    if (editor_mode_ == EDIT_CANVAS) {

        if (!current_canvas_)
            return ret;

        Group *sourceNode = current_canvas_->group(mode_); // groups_[View::GEOMETRY]

        // make sure matrix transform of stored status is updated
        current_canvas_->stored_status_->update(0);

        // grab coordinates in source-root reference frame
        const glm::mat4 source_scale = glm::scale(glm::identity<glm::mat4>(),
                                                glm::vec3(1.f / current_canvas_->frame()->aspectRatio(), 1.f, 1.f));
        const glm::mat4 scene_to_source_transform = source_scale * glm::inverse(current_canvas_->stored_status_->transform_);
        const glm::mat4 source_to_scene_transform = glm::inverse(scene_to_source_transform);

        std::ostringstream info;
        // which manipulation to perform?

        if (pick.first)  {

            // which corner was picked ?
            glm::vec2 corner = glm::round(pick.second);

            // keep transform from source center to opposite corner
            const glm::mat4 source_to_corner_transform = glm::translate(glm::identity<glm::mat4>(), glm::vec3(corner, 0.f));

            // transformation from scene to corner:
            const glm::mat4 scene_to_corner_transform = source_to_corner_transform * scene_to_source_transform;
            const glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform);


            // Create context for handle manipulation functions
            GeometryHandleManipulation::HandleGrabContext ctx = {
                sourceNode,                     // targetNode
                current_canvas_->stored_status_,              // stored_status
                current_canvas_->frame(),                     // frame
                scene_from,                     // scene_from
                scene_to,                       // scene_to
                scene_to_source_transform,      // scene_to_target_transform
                source_to_scene_transform,      // target_to_scene_transform
                scene_to_corner_transform,      // scene_to_corner_transform
                corner_to_scene_transform,      // corner_to_scene_transform
                corner,                         // corner
                pick,                           // pick
                grid,                           // grid
                info,                           // info
                ret,                            // cursor
                current_canvas_->handles_[mode_],             // handles
                canvas_overlay_crop_,                  // overlay_crop
                overlay_scaling_,               // overlay_scaling
                overlay_scaling_cross_,         // overlay_scaling_cross
                overlay_rotation_,              // overlay_rotation
                overlay_rotation_fix_,          // overlay_rotation_fix
                overlay_rotation_clock_hand_    // overlay_rotation_clock_hand
            };

            if (pick.first == current_canvas_->handles_[mode_][Handles::CROP_H]) {
                GeometryHandleManipulation::handleCropH(ctx);
            }
            // Vertical CROP
            else if (pick.first == current_canvas_->handles_[mode_][Handles::CROP_V]) {
                GeometryHandleManipulation::handleCropV(ctx);
            }
        }

        // request update
        current_canvas_->touch();

        // store action in history
        current_action_ = current_canvas_->name() + ": " + info.str();

        // update cursor
        ret.info = info.str();
        return ret;
    }

    // if no source is given when editing sources...
    if (!s && editor_mode_ == EDIT_SOURCES) {

        // ..possibly grabing the selection overlay handles
        if (overlay_selection_ && overlay_selection_active_ ) {

            // rotation center to selection position
            glm::mat4 T = glm::translate(glm::identity<glm::mat4>(), overlay_selection_stored_status_->translation_);
            glm::vec4 selection_from = glm::inverse(T) * glm::vec4( scene_from,  1.f );
            glm::vec4 selection_to   = glm::inverse(T) * glm::vec4( scene_to,  1.f );

            // calculate scaling of selection
            float factor = glm::length( glm::vec2( selection_to ) ) / glm::length( glm::vec2( selection_from ) );
            glm::mat4 S = glm::scale(glm::identity<glm::mat4>(), glm::vec3(factor, factor, 1.f));

            // if interaction with selection SCALING handle
            if (pick.first == overlay_selection_scale_) {

                // show manipulation overlay
                overlay_scaling_cross_->visible_ = true;
                overlay_scaling_grid_->visible_ = false;
                overlay_scaling_->visible_ = true;
                overlay_scaling_->translation_.x = overlay_selection_stored_status_->translation_.x;
                overlay_scaling_->translation_.y = overlay_selection_stored_status_->translation_.y;
                overlay_scaling_->rotation_.z = overlay_selection_stored_status_->rotation_.z;
                overlay_scaling_->update(0);
                overlay_scaling_cross_->copyTransform(overlay_scaling_);
                overlay_scaling_->color = overlay_selection_icon_->color;
                overlay_scaling_cross_->color = overlay_selection_icon_->color;

                //
                // Manipulate the scaling handle in the SCENE coordinates to apply grid snap
                //
                if ( grid->active() ) {
                    glm::vec3 handle = glm::vec3(1.f, -1.f, 0.f);
                    // Compute handle coordinates into SCENE reference frame
                    handle = overlay_selection_stored_status_->transform_ * glm::vec4( handle, 1.f );
                    // move the handle we hold by the mouse translation (in scene reference frame)
                    handle = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * glm::vec4( handle, 1.f );
                    // snap handle coordinates to grid (if active)
                    handle = grid->snap(handle);
                    // Compute handle coordinates back in SOURCE reference frame
                    handle = glm::inverse(overlay_selection_stored_status_->transform_) * glm::vec4( handle,  1.f );
                    // The scaling factor is computed by dividing new handle coordinates with the ones before transform
                    glm::vec3 handle_scaling = glm::vec3(handle) / glm::vec3(1.f, -1.f, 1.f);
                    S = glm::scale(glm::identity<glm::mat4>(), handle_scaling);
                }

                // apply to selection overlay
                glm::vec4 vec = S * glm::vec4( overlay_selection_stored_status_->scale_, 0.f );
                overlay_selection_->scale_ = glm::vec3(vec);

                // apply to selection sources
                // NB: complete transform matrix (right to left) : move to center, scale and move back
                glm::mat4 M = T * S * glm::inverse(T);
                applySelectionTransform(M);

                // store action in history
                current_action_ = "Scale selection";
                ret.type = Cursor_ResizeNWSE;
            }
            // ...or if interaction with selection ROTATION handle
            else if (pick.first == overlay_selection_rotate_) {

                // show manipulation overlay
                overlay_rotation_->visible_ = true;
                overlay_rotation_->translation_.x = overlay_selection_stored_status_->translation_.x;
                overlay_rotation_->translation_.y = overlay_selection_stored_status_->translation_.y;
                overlay_rotation_->update(0);
                overlay_rotation_->color = overlay_selection_icon_->color;
                overlay_rotation_fix_->visible_ = false;
                overlay_rotation_fix_->copyTransform(overlay_rotation_);
                overlay_rotation_fix_->color = overlay_selection_icon_->color;

                // Swap grid to rotation, shifted at center of source
                rotation_grid_->setActive( grid->active() );
                translation_grid_->setActive( false );
                grid = rotation_grid_;
                grid->root()->translation_ = overlay_selection_stored_status_->translation_;

                // prepare variables
                const float diagonal = glm::length( glm::vec2(overlay_selection_stored_status_->scale_));
                glm::vec2 handle_polar = glm::vec2( diagonal, 0.f);

                // compute rotation angle
                float angle = glm::orientedAngle( glm::normalize(glm::vec2(selection_from)), glm::normalize(glm::vec2(selection_to)));
                handle_polar.y = overlay_selection_stored_status_->rotation_.z + angle;

                // compute scaling of diagonal to reach new coordinates
                handle_polar.x *= factor;

                // snap polar coordiantes (diagonal lenght, angle)
                if ( grid->active() ) {
                    handle_polar = glm::round( handle_polar / grid->step() ) * grid->step();
                    // prevent null size
                    handle_polar.x = glm::max( grid->step().x,  handle_polar.x );
                }

                // cancel scaling with SHIFT modifier key
                if (UserInterface::manager().shiftModifier()) {
                    overlay_rotation_fix_->visible_ = true;
                    handle_polar.x = 1.f;
                }
                else
                    handle_polar.x /= diagonal ;

                S = glm::scale(glm::identity<glm::mat4>(), glm::vec3(handle_polar.x, handle_polar.x, 1.f));

                // apply to selection overlay
                glm::vec4 vec = S * glm::vec4( overlay_selection_stored_status_->scale_, 0.f );
                overlay_selection_->scale_ = glm::vec3(vec);
                overlay_selection_->rotation_.z = handle_polar.y;

                // apply to selection sources
                // NB: complete transform matrix (right to left) : move to center, rotate, scale and move back
                angle = handle_polar.y - overlay_selection_stored_status_->rotation_.z;
                glm::mat4 R = glm::rotate(glm::identity<glm::mat4>(), angle, glm::vec3(0.f, 0.f, 1.f) );
                glm::mat4 M = T * S * R * glm::inverse(T);
                applySelectionTransform(M);

                // store action in history
                current_action_ = "Scale and rotate selection";
                ret.type = Cursor_Hand;
            }
        }

        return ret;
    }

    // normal source grab 
    Group *sourceNode = s->group(mode_); // groups_[View::GEOMETRY]

    // make sure matrix transform of stored status is updated
    s->stored_status_->update(0);

    // grab coordinates in source-root reference frame
    const glm::mat4 source_scale = glm::scale(glm::identity<glm::mat4>(),
                                              glm::vec3(1.f / s->frame()->aspectRatio(), 1.f, 1.f));
    const glm::mat4 scene_to_source_transform = source_scale * glm::inverse(s->stored_status_->transform_);
    const glm::mat4 source_to_scene_transform = glm::inverse(scene_to_source_transform);

    // which manipulation to perform?
    std::ostringstream info;
    if (pick.first)  {

        // which corner was picked ?
        glm::vec2 corner = glm::round(pick.second);

        // keep transform from source center to opposite corner
        const glm::mat4 source_to_corner_transform = glm::translate(glm::identity<glm::mat4>(), glm::vec3(corner, 0.f));

        // transformation from scene to corner:
        const glm::mat4 scene_to_corner_transform = source_to_corner_transform * scene_to_source_transform;
        const glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform);


        // Create context for handle manipulation functions
        GeometryHandleManipulation::HandleGrabContext ctx = {
            sourceNode,                     // targetNode
            s->stored_status_,              // stored_status
            s->frame(),                     // frame
            scene_from,                     // scene_from
            scene_to,                       // scene_to
            scene_to_source_transform,      // scene_to_target_transform
            source_to_scene_transform,      // target_to_scene_transform
            scene_to_corner_transform,      // scene_to_corner_transform
            corner_to_scene_transform,      // corner_to_scene_transform
            corner,                         // corner
            pick,                           // pick
            grid,                           // grid
            info,                           // info
            ret,                            // cursor
            s->handles_[mode_],             // handles
            overlay_crop_,                  // overlay_crop
            overlay_scaling_,               // overlay_scaling
            overlay_scaling_cross_,         // overlay_scaling_cross
            overlay_rotation_,              // overlay_rotation
            overlay_rotation_fix_,          // overlay_rotation_fix
            overlay_rotation_clock_hand_    // overlay_rotation_clock_hand
        };

        // picking on a Node
        if (pick.first == s->handles_[mode_][Handles::NODE_LOWER_LEFT]) {
            GeometryHandleManipulation::handleNodeLowerLeft(ctx);
        }
        else if (pick.first == s->handles_[mode_][Handles::NODE_UPPER_LEFT]) {
            GeometryHandleManipulation::handleNodeUpperLeft(ctx);
        }
        else if ( pick.first == s->handles_[mode_][Handles::NODE_LOWER_RIGHT] ) {
            GeometryHandleManipulation::handleNodeLowerRight(ctx);
        }
        else if (pick.first == s->handles_[mode_][Handles::NODE_UPPER_RIGHT]) {
            GeometryHandleManipulation::handleNodeUpperRight(ctx);
        }
        // horizontal CROP
        else if (pick.first == s->handles_[mode_][Handles::CROP_H]) {
            GeometryHandleManipulation::handleCropH(ctx);
        }
        // Vertical CROP
        else if (pick.first == s->handles_[mode_][Handles::CROP_V]) {
            GeometryHandleManipulation::handleCropV(ctx);
        }
        // pick the corner rounding handle
        else if (pick.first == s->handles_[mode_][Handles::ROUNDING]) {
            GeometryHandleManipulation::handleRounding(ctx);
        }
        // picking on the resizing handles in the corners RESIZE CORNER
        else if ( pick.first == s->handles_[mode_][Handles::RESIZE] ) {
            GeometryHandleManipulation::handleResize(ctx);
        }
        // picking on the BORDER RESIZING handles left or right
        else if ( pick.first == s->handles_[mode_][Handles::RESIZE_H] ) {
            GeometryHandleManipulation::handleResizeH(ctx);
        }
        // picking on the BORDER RESIZING handles top or bottom
        else if ( pick.first == s->handles_[mode_][Handles::RESIZE_V] ) {
            GeometryHandleManipulation::handleResizeV(ctx);
        }
        // picking on the CENTRER SCALING handle
        else if ( pick.first == s->handles_[mode_][Handles::SCALE] ) {
            GeometryHandleManipulation::handleScale(ctx);
        }
        // picking on the rotating handle
        else if ( pick.first == s->handles_[mode_][Handles::ROTATE] ) {
            GeometryHandleManipulation::handleRotate(ctx);
        }
        // picking anywhere but on a handle: user wants to move the source
        else {

            // Default is to grab the center (0,0) of the source
            glm::vec3 handle(0.f);
            glm::vec3 offset(0.f);

            // Snap corner with SHIFT
            if (UserInterface::manager().shiftModifier()) {
                // get corner closest representative of the quadrant of the picking point
                handle = glm::vec3( glm::sign(pick.second), 0.f);
                // remember the offset for adjustment of translation to this corner
                offset = source_to_scene_transform * glm::vec4(handle, 0.f);
            }

            // Compute target coordinates of manipulated handle into SCENE reference frame
            glm::vec3 source_target = source_to_scene_transform * glm::vec4(handle, 1.f);

            // apply translation of target in SCENE
            source_target = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * glm::vec4(source_target, 1.f);

            // snap coordinates to grid (if active)
            if ( grid->active() )
                // snap coordinate in scene
                source_target = grid->snap(source_target);

            // Apply translation to the source
            sourceNode->translation_ = source_target - offset;

            //
            // grab all others in selection
            //
            // compute effective translation of current source s
            source_target = sourceNode->translation_ - s->stored_status_->translation_;
            // loop over selection
            for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
                if ( *it != s && !(*it)->locked() ) {
                    // translate and request update
                    (*it)->group(mode_)->translation_ = (*it)->stored_status_->translation_ + source_target;
                    (*it)->touch();
                }
            }

            // Show center overlay for POSITION
            overlay_position_->visible_ = true;
            overlay_position_->translation_.x = sourceNode->translation_.x;
            overlay_position_->translation_.y = sourceNode->translation_.y;
            overlay_position_->update(0);

            // Show move cursor
            ret.type = Cursor_ResizeAll;
            info << "Position " << std::fixed << std::setprecision(3) << sourceNode->translation_.x;
            info << ", "  << sourceNode->translation_.y ;
        }
    }

    // request update
    s->touch();

    // store action in history
    current_action_ = s->name() + ": " + info.str();

    // update cursor
    ret.info = info.str();
    return ret;
}

void GeometryView::initiate()
{
    if (editor_mode_ == EDIT_SOURCES)
        View::initiate();
    else if (!current_action_ongoing_) {
        // all sources store their status at initiation of an action
        for (auto sit = Canvas::manager().begin();
             sit != Canvas::manager().end(); ++sit)
            (*sit)->store(mode_);
        // initiated
        current_action_ = "";
        current_action_ongoing_ = true;
    }
}

void GeometryView::terminate(bool force)
{
    if (editor_mode_ == EDIT_SOURCES)
        View::terminate(force);
    else if (current_action_ongoing_) {
        // terminated
        current_action_ = "";
        current_action_ongoing_ = false;
    }

    // hide all view overlays
    overlay_position_->visible_       = false;
    overlay_position_cross_->visible_ = false;
    overlay_rotation_clock_->visible_ = false;
    overlay_rotation_clock_hand_->visible_ = false;
    overlay_rotation_fix_->visible_   = false;
    overlay_rotation_->visible_       = false;
    overlay_scaling_grid_->visible_   = false;
    overlay_scaling_cross_->visible_  = false;
    overlay_scaling_->visible_        = false;
    overlay_crop_->visible_           = false;

    // restore possible color change after selection operation
    overlay_rotation_->color = glm::vec4(1.f, 1.f, 1.f, 0.8f);
    overlay_rotation_fix_->color = glm::vec4(1.f, 1.f, 1.f, 0.8f);
    overlay_rotation_clock_tic_->color = glm::vec4(1.f, 1.f, 1.f, 0.8f);
    overlay_scaling_->color = glm::vec4(1.f, 1.f, 1.f, 0.8f);
    overlay_scaling_cross_->color =  glm::vec4(1.f, 1.f, 1.f, 0.8f);

    // restore of all handles overlays
    glm::vec2 c(0.f, 0.f);
    for (auto sit = Mixer::manager().session()->begin();
         sit != Mixer::manager().session()->end(); ++sit){
        (*sit)->handles_[mode_][Handles::RESIZE]->overlayActiveCorner(c);
        (*sit)->handles_[mode_][Handles::RESIZE_H]->overlayActiveCorner(c);
        (*sit)->handles_[mode_][Handles::RESIZE_V]->overlayActiveCorner(c);
        for (auto h = 0; h < 15; ++h)
            (*sit)->handles_[mode_][h]->visible_ = true;
    }

    overlay_selection_active_ = false;

    // restore all handles canvas
    canvas_overlay_crop_->visible_   = false;
    for (auto sit = Canvas::manager().begin();
         sit != Canvas::manager().end(); ++sit){
        (*sit)->handles_[mode_][Handles::MENU]->visible_ = true;
        (*sit)->handles_[mode_][Handles::CROP_H]->visible_ = true;
        (*sit)->handles_[mode_][Handles::CROP_V]->visible_ = true;
    }

    // reset grid
    adaptGridToSource();
}


View::Cursor GeometryView::over(glm::vec2 pos)
{
    View::Cursor ret = Cursor();

    // unproject mouse coordinate into scene coordinates
    glm::vec3 scene_point_ = Rendering::manager().unProject(pos);

    // picking visitor traverses the scene
    PickingVisitor pv(scene_point_, false);
    scene.accept(pv);

    //
    // mouse over current source
    //
    Source *current = Mixer::manager().currentSource();
    if (current != nullptr) {
        // reset mouse over handles
        current->handles_[mode_][Handles::EDIT_CROP]->color = glm::vec4(COLOR_HIGHLIGHT_SOURCE, 0.6f);
        current->handles_[mode_][Handles::EDIT_SHAPE]->color = glm::vec4(COLOR_HIGHLIGHT_SOURCE, 0.6f);

        // picking visitor found nodes?
        if (!pv.empty()) {
            // find pick in the current source
            std::pair<Node *, glm::vec2> pick = {nullptr, glm::vec2(0.f)};
            auto itp = pv.rbegin();
            for (; itp != pv.rend(); ++itp){
                // test if source contains this node
                Source::hasNode is_in_source((*itp).first );
                if ( is_in_source( current ) ){
                    // a node in the current source was clicked !
                    pick = *itp;
                    break;
                }
            }
            // mouse over handles
            if (pick.first == current->handles_[mode_][Handles::EDIT_CROP])
                current->handles_[mode_][Handles::EDIT_CROP]->color = glm::vec4(COLOR_HIGHLIGHT_SOURCE, 1.f);
            else if (pick.first == current->handles_[mode_][Handles::EDIT_SHAPE])
                current->handles_[mode_][Handles::EDIT_SHAPE]->color = glm::vec4(COLOR_HIGHLIGHT_SOURCE, 1.f);
        }
    }

    return ret;
}

#define MAX_DURATION 1000.f
#define MIN_SPEED_A 0.005f
#define MAX_SPEED_A 0.5f

void GeometryView::arrow (glm::vec2 movement)
{
    static float _duration = 0.f;
    static glm::vec2 _from(0.f);
    static glm::vec2 _displacement(0.f);

    Source *current = Mixer::manager().currentSource();

    if (!current && !Mixer::selection().empty())
        Mixer::manager().setCurrentSource( Mixer::selection().back() );

    if (current) {

        if (current_action_ongoing_) {

            // add movement to displacement
            _duration += dt_;
            const float speed = MIN_SPEED_A + (MAX_SPEED_A - MIN_SPEED_A) * glm::min(1.f,_duration / MAX_DURATION);
            _displacement += movement * dt_ * speed;

            // set coordinates of target
            glm::vec2 _to  = _from + _displacement;

            // update mouse pointer action
            MousePointer::manager().active()->update(_to, dt_ / 1000.f);

            // simulate mouse grab
            grab(current, _from, MousePointer::manager().active()->target(),
                 std::make_pair(current->group(mode_), glm::vec2(0.f) ) );

            // draw mouse pointer effect
            MousePointer::manager().active()->draw();
        }
        else {

            if (UserInterface::manager().altModifier() || Settings::application.mouse_pointer_lock)
                MousePointer::manager().setActiveMode( (Pointer::Mode) Settings::application.mouse_pointer );
            else
                MousePointer::manager().setActiveMode( Pointer::POINTER_DEFAULT );

            // reset
            _duration = 0.f;
            _displacement = glm::vec2(0.f);

            // initiate view action and store status of source
            initiate();

            // get coordinates of source and set this as start of mouse position
            _from = glm::vec2( Rendering::manager().project(current->group(mode_)->translation_, scene.root()->transform_) );

            // Initiate mouse pointer action
            MousePointer::manager().active()->initiate(_from);
        }
    }
    else {
        terminate(true);

        // reset
        _duration = 0.f;
        _from = glm::vec2(0.f);
        _displacement = glm::vec2(0.f);
    }

}

void GeometryView::updateSelectionOverlay(glm::vec4 color)
{
    View::updateSelectionOverlay(color);

    // create first
    if (overlay_selection_scale_ == nullptr) {

        overlay_selection_stored_status_ = new Group;
        overlay_selection_scale_ = new Handles(Handles::SCALE);
        overlay_selection_->attach(overlay_selection_scale_);
        overlay_selection_rotate_ = new Handles(Handles::ROTATE);
        overlay_selection_->attach(overlay_selection_rotate_);
    }

    if (overlay_selection_->visible_) {

        if ( !overlay_selection_active_) {

            // calculate ORIENTED bbox on selection
            GlmToolkit::OrientedBoundingBox selection_box = BoundingBoxVisitor::OBB(Mixer::selection().getCopy(), scene.ws(), mode_);

            // apply transform
            overlay_selection_->rotation_ = selection_box.orientation;
            overlay_selection_->scale_ = selection_box.aabb.scale();
            glm::mat4 rot = glm::rotate(glm::identity<glm::mat4>(), selection_box.orientation.z, glm::vec3(0.f, 0.f, 1.f) );
            glm::vec4 vec = rot * glm::vec4(selection_box.aabb.center(), 1.f);
            overlay_selection_->translation_ = glm::vec3(vec);
        }

        // cosmetics
        overlay_selection_scale_->color = overlay_selection_icon_->color;
        overlay_selection_rotate_->color = overlay_selection_icon_->color;
    }
}


void GeometryView::setCurrentCanvas(Source *c)
{
    // check if current canvas is still valid
    SourceList::iterator sit = std::find(Canvas::manager().begin(), 
                            Canvas::manager().end(), current_canvas_);
    if ( sit == Canvas::manager().end() ) 
        // invalid canvas; set current to null
        current_canvas_ = nullptr;

    // un-select current canvas if null given
    if (c == nullptr) {
        if (current_canvas_ != nullptr) {
            current_canvas_->setMode(Source::VISIBLE);
            current_canvas_ = nullptr;
        }
        return;
    } 
    else if (current_canvas_ == c) {
        // nothing to change if same canvas
        return;
    }

    current_canvas_ = c;
    current_canvas_->setMode(Source::CURRENT);

    current_canvas_->manipulator_->setActive(1);
    current_canvas_->handles_[mode_][Handles::NODE_LOWER_RIGHT]->visible_ = false;
    current_canvas_->handles_[mode_][Handles::NODE_LOWER_LEFT]->visible_ = false;
    current_canvas_->handles_[mode_][Handles::NODE_UPPER_RIGHT]->visible_ = false;
    current_canvas_->handles_[mode_][Handles::NODE_UPPER_LEFT]->visible_ = false;
    current_canvas_->handles_[mode_][Handles::ROUNDING]->visible_ = false;
    current_canvas_->handles_[mode_][Handles::EDIT_CROP]->visible_ = false;
    current_canvas_->handles_[mode_][Handles::CROP_H]->visible_ = true;
    current_canvas_->handles_[mode_][Handles::CROP_V]->visible_ = true;
    current_canvas_->handles_[mode_][Handles::MENU]->visible_ = true;
}