/*
 * 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 "Source/MediaSource.h"
#include "Source/SourceList.h"
#include <string>
#include <algorithm>
#include <list>
#include <tuple>

#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/vector_angle.hpp>

// ImGui
#include "imgui.h"
#include "Toolkit/ImGuiToolkit.h"
#include "imgui_internal.h"

#include "defines.h"
#include "Settings.h"
#include "Source/Source.h"
#include "Mixer.h"
#include "Source/SourceCallback.h"
#include "ControlManager.h"
#include "Metronome.h"
#include "MediaPlayer.h"

#include "InputMappingWindow.h"

InputMappingWindow::InputMappingWindow() : WorkspaceWindow("InputMappingInterface")
{
    input_mode = { ICON_FA_KEYBOARD "  Keyboard",
                   ICON_FA_CALCULATOR "   Numpad" ,
                   ICON_FA_TABLET_ALT "   TouchOSC" ,
                   ICON_FA_GAMEPAD "  Gamepad",
                   ICON_FA_CLOCK "   Timer"  };
    current_input_for_mode = { INPUT_KEYBOARD_FIRST, INPUT_NUMPAD_FIRST, INPUT_MULTITOUCH_FIRST, INPUT_JOYSTICK_FIRST, INPUT_TIMER_FIRST };
    current_input_ = current_input_for_mode[Settings::application.mapping.mode];
}

void InputMappingWindow::setVisible(bool on)
{
    // restore workspace to show the window
    if (WorkspaceWindow::clear_workspace_enabled) {
        WorkspaceWindow::restoreWorkspace(on);
        // do not change status if ask to hide (consider user asked to toggle because the window was not visible)
        if (!on)  return;
    }

    if (Settings::application.widget.inputs_view > 0 && Settings::application.widget.inputs_view != Settings::application.current_view) {
        Settings::application.widget.inputs_view = -1;
        on = true;
    }

    Settings::application.widget.inputs = on;
}

bool InputMappingWindow::Visible() const
{
    return ( Settings::application.widget.inputs
             && (Settings::application.widget.inputs_view < 0 || Settings::application.widget.inputs_view == Settings::application.current_view )
             );
}

///
/// Draw a combo box listing all sources and all batch of the current session
/// Returns a Target variant : non-assigned by default (std::monostate), or a Source, or a Batch
/// If current element is indicated, it is displayed first
///
Target InputMappingWindow::ComboSelectTarget(const Target &current)
{
    Target selected;
    std::string label = "Select";

    // depending on variant, fill the label of combo
    if (current.index() > 0) {
        if (Source * const* v = std::get_if<Source *>(&current)) {
            label = std::string((*v)->initials()) + " - " + (*v)->name();
        }
        else if ( const size_t* v = std::get_if<size_t>(&current)) {
            label = std::string("Batch #") + std::to_string(*v);
        }
        else {
            label = "Current source";
        }
    }

    if (ImGui::BeginCombo("##ComboSelectSource", label.c_str()) )
    {
        if (ImGui::Selectable( "Current source" )) {
            selected = Current{};
        }
        Session *ses = Mixer::manager().session();
        for (auto sit = ses->begin(); sit != ses->end(); ++sit) {
            label = std::string((*sit)->initials()) + " - " + (*sit)->name();
            if (ImGui::Selectable( label.c_str() )) {
                selected = *sit;
            }
        }
        for (size_t b = 0; b < ses->numBatch(); ++b){
            label = std::string("Batch #") + std::to_string(b);
            if (ImGui::Selectable( label.c_str() )) {
                selected = b;
            }
        }

        ImGui::EndCombo();
    }

    return selected;
}

uint InputMappingWindow::ComboSelectCallback(uint current, bool imageprocessing, bool ismediaplayer)
{
    const char* callback_names[24] = { "Select",
                                       ICON_FA_BULLSEYE "  Alpha",
                                       ICON_FA_BULLSEYE "  Loom",
                                       ICON_FA_OBJECT_UNGROUP "  Geometry",
                                       ICON_FA_OBJECT_UNGROUP "  Grab",
                                       ICON_FA_OBJECT_UNGROUP "  Resize",
                                       ICON_FA_OBJECT_UNGROUP "  Turn",
                                       ICON_FA_LAYER_GROUP "  Depth",
                                       ICON_FA_PLAY_CIRCLE "  Play",
                                       ICON_FA_PLAY_CIRCLE "  Speed",
                                       ICON_FA_PLAY_CIRCLE "  Fast forward",
                                       ICON_FA_PLAY_CIRCLE "  Seek",
                                       ICON_FA_PLAY_CIRCLE "  Flag",
                                       "  None",
                                       "  None",
                                       "  None",
                                       ICON_FA_PALETTE "  Gamma",
                                       ICON_FA_PALETTE "  Brightness",
                                       ICON_FA_PALETTE "  Contrast",
                                       ICON_FA_PALETTE "  Saturation",
                                       ICON_FA_PALETTE "  Hue",
                                       ICON_FA_PALETTE "  Threshold",
                                       ICON_FA_PALETTE "  Invert",
                                       "  None"
    };

    if (!ismediaplayer && current > SourceCallback::CALLBACK_PLAY && current <= SourceCallback::CALLBACK_FLAG) 
        return SourceCallback::CALLBACK_INVALID;

    if (!imageprocessing && current >= SourceCallback::CALLBACK_GAMMA) 
        return SourceCallback::CALLBACK_INVALID;

    uint selected = 0;
    if (ImGui::BeginCombo("##ComboSelectCallback", callback_names[current]) ) {
        for (uint i = SourceCallback::CALLBACK_ALPHA; 
            i <= (ismediaplayer ? SourceCallback::CALLBACK_FLAG : SourceCallback::CALLBACK_PLAY) ; ++i){
            if ( ImGui::Selectable( callback_names[i]) ) {
                selected = i;
            }
        }
        if (imageprocessing) {
            for (uint i = SourceCallback::CALLBACK_GAMMA; i <= SourceCallback::CALLBACK_INVERT; ++i){
                if ( ImGui::Selectable( callback_names[i]) ) {
                    selected = i;
                }
            }
        }
        ImGui::EndCombo();
    }

    return selected;
}

struct ClosestIndex
{
    int index;
    float val;
    ClosestIndex (float v) { val = v; index = 0; }
    void operator()(float v) { if (v < val) ++index; }
};

void testButton(SourceCallback *callback, const Target &target, 
    const std::string& tooltip, int i, int j)
{
    if (ImGui::IsItemHovered())
            ImGui::SetTooltip("%s", tooltip.c_str());
        
    // Define the triplet type
    using SourceCallbackTriplet = std::tuple<Source*, SourceCallback*, SourceCallback*>;

    // Create a list of triplets to contain list of source and their callbacks
    static std::list<SourceCallbackTriplet> triplets;

    ImGuiToolkit::IconButton(i, j, "Press to test");
    if (ImGui::IsItemActivated())
    {
        triplets.clear();
        // prepare all triplets depending on target type
        if (Source * const * v= std::get_if<Source *>(&target)) {
            if (*v != nullptr) {
                SourceCallback *fwd = callback->clone();
                SourceCallback *bwd = nullptr;
                if (callback->bidirectional())
                    bwd = callback->reverse(*v);
                triplets.push_back( std::make_tuple(*v, fwd, bwd) );
            }
        }
        else if ( const size_t* v = std::get_if<size_t>(&target)) {
            SourceList sl = Mixer::manager().session()->getBatch(*v);
            for (auto sid = sl.begin(); sid != sl.end(); ++sid){
                SourceList::const_iterator sit = Mixer::manager().session()->find(*sid);
                if ( sit != Mixer::manager().session()->end()) {
                    SourceCallback *fwd = callback->clone();
                    SourceCallback *bwd = nullptr;
                    if (callback->bidirectional())
                        bwd = callback->reverse(*sit);
                    triplets.push_back( std::make_tuple(*sit, fwd, bwd) );
                }
            }
        }
        // apply all forwards callbacks
        for (const auto& [src, fwd, bwd] : triplets) {
            src->call( fwd );
        }

    }
    if (ImGui::IsItemDeactivated() && !triplets.empty() ){
        // revert all callbacks
        for (const auto& [src, fwd, bwd] : triplets) {
            if ( bwd != nullptr )
                src->call( bwd, true );
            else if (fwd != nullptr)
                src->finish( fwd );
        }
        // finished testing: clear the triplet list
        triplets.clear();
    }
}

void InputMappingWindow::SliderParametersCallback(SourceCallback *callback, const Target &target)
{
    const float right_align = -1.05f * ImGui::GetTextLineHeightWithSpacing();
    static const char *press_tooltip[3] = {"Key Press\nApply value on key press",
                              "Key Down\nApply value on key down,\nrevert on key up",
                              "Repeat\nMaintain key down to repeat and iterate" };
    static std::vector< std::pair<int,int> > speed_icon = { {18,15}, {17,15}, {16,15}, {15,15}, {14,15} };
    static std::vector< std::string > speed_tooltip = { "Fastest\n0 ms", "Fast\n60 ms", "Smooth\n120 ms", "Slow\n240 ms", "Slowest\n500 ms" };
    static std::vector< float > speed_values = { 0.f, 60.f, 120.f, 240.f, 500.f };

    switch ( callback->type() ) {
    case SourceCallback::CALLBACK_ALPHA:
    {
        SetAlpha *edited = static_cast<SetAlpha*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        ImGui::SetNextItemWidth(right_align);
        if (ImGui::SliderFloat("##CALLBACK_ALPHA", &val, -1.f, 1.f, "%.2f"))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Alpha value to set if the source is\nvisible (1.0), transparent (0.0),\nor innactive (-1.0)", 18, 12);
    }
        break;
    case SourceCallback::CALLBACK_LOOM:
    {
        ImGuiToolkit::Indication(press_tooltip[2], 18, 5);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        Loom *edited = static_cast<Loom*>(callback);
        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        if (ImGui::SliderFloat("##CALLBACK_LOOM", &val, -1.f, 1.f, "%.2f", 2.f))
            edited->setValue(val);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Increment alpha to make the source more visible (>0) or more transparent (<0)", 19, 12);
    }
        break;
    case SourceCallback::CALLBACK_GEOMETRY:
    {
        SetGeometry *edited = static_cast<SetGeometry*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        if (target.index() > 0) {
            // 1. Case of variant as Source pointer
            if (Source * const* v = std::get_if<Source *>(&target)) {
                // Button to capture the source current geometry
                ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
                if (ImGui::Button("Capture", ImVec2(right_align, 0))) {
                    edited->setTarget( (*v)->group(View::GEOMETRY) );
                }
            }
            // 2. Case of variant as index of batch
            else if ( const size_t* v = std::get_if<size_t>(&target)) {

                std::vector<SourceIdList> _batch = Mixer::manager().session()->getAllBatch();

                std::string label = "Capture";
                // Combo box to set which source to capture
                if (ImGui::BeginCombo("##ComboSelectGeometryCapture", label.c_str()) )
                {
                    if ( *v < _batch.size() )
                    {
                        for (auto sid = _batch[*v].begin(); sid != _batch[*v].end(); ++sid){
                            SourceList::iterator sit = Mixer::manager().session()->find(*sid);
                            if ( sit != Mixer::manager().session()->end() ) {

                                label = std::string((*sit)->initials()) + " - " + (*sit)->name();
                                // C to capture the source current geometry
                                if (ImGui::Selectable( label.c_str() )) {
                                    edited->setTarget( (*sit)->group(View::GEOMETRY) );
                                }
                            }
                        }
                    }

                    ImGui::EndCombo();
                }
            }

            ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
            testButton(edited, target, "Capture source geometry to restore it later (position, scale and rotation).", 1, 16);
        }
        else {

            ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
            ImGui::TextDisabled("Invalid");
        }
    }
        break;

    case SourceCallback::CALLBACK_GRAB:
    {
        ImGuiToolkit::Indication(press_tooltip[2], 18, 5);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        Grab *edited = static_cast<Grab*>(callback);
        float val[2] = {edited->value().x, edited->value().y};
        ImGui::SetNextItemWidth(right_align);
        if (ImGui::SliderFloat2("##CALLBACK_GRAB", val, -2.f, 2.f, "%.2f"))
            edited->setValue( glm::vec2(val[0], val[1]));
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Increment vector (x,y) to move the source horizontally and vertically.", 6, 15);
    }
        break;

    case SourceCallback::CALLBACK_RESIZE:
    {
        ImGuiToolkit::Indication(press_tooltip[2], 18, 5);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        Resize *edited = static_cast<Resize*>(callback);
        float val[2] = {edited->value().x, edited->value().y};
        ImGui::SetNextItemWidth(right_align);
        if (ImGui::SliderFloat2("##CALLBACK_RESIZE", val, -2.f, 2.f, "%.2f"))
            edited->setValue( glm::vec2(val[0], val[1]));
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Increment vector (x,y) to scale the source horizontally and vertically.", 2, 15);

    }
        break;

    case SourceCallback::CALLBACK_TURN:
    {
        ImGuiToolkit::Indication(press_tooltip[2], 18, 5);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        Turn *edited = static_cast<Turn*>(callback);
        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        if ( ImGui::SliderAngle("##CALLBACK_TURN", &val, -180.f, 180.f) )
            edited->setValue(val );

        ImGui::SameLine(0, IMGUI_SAME_LINE / 3);
        testButton(edited, target, "Rotation speed (\u00B0/s) to turn the source clockwise (>0) or counterclockwise (<0)", 18, 9);
    }
        break;

    case SourceCallback::CALLBACK_DEPTH:
    {
        SetDepth *edited = static_cast<SetDepth*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_DEPTH", &val, 11.9f, 0.1f, "%.1f"))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Depth value to place the source front (12) or back (0) in the scene.", 11, 16);
    }
        break;

    case SourceCallback::CALLBACK_PLAY:
    {
        Play *edited = static_cast<Play*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);

        int val = edited->value() ? 1 : 0;
        ImGui::SetNextItemWidth(right_align);
        if (ImGui::SliderInt("##CALLBACK_PLAY", &val, 0, 1, "Pause  |   Play "))
            edited->setValue(val>0);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Play or pause the source.", 12, 7);
    }
        break;

    case SourceCallback::CALLBACK_PLAYSPEED:
    {
        PlaySpeed *edited = static_cast<PlaySpeed*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_PLAYSPEED", &val, -10.f, 10.f, UNICODE_MULTIPLY " %.2f"))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Factor to multiply the playback speed of a video source.", 0, 12);
    }
        break;

    case SourceCallback::CALLBACK_PLAYFFWD:
    {
        PlayFastForward *edited = static_cast<PlayFastForward*>(callback);

        ImGuiToolkit::Indication(press_tooltip[2], 18, 5);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);

        int val = (int) edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderInt("##CALLBACK_PLAYFFWD", &val, 30, 1000, "%d ms"))
            edited->setValue( MAX(1, val) );

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Step increment (in miliseconds) to jump fast-forward in a video source.", 13, 7);
    }
        break;

    case SourceCallback::CALLBACK_SEEK:
    {
        Seek *edited = static_cast<Seek*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);
        
        // Text input field for MM:SS:MS seek target time
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);

        guint64 duration = GST_SECOND * 1000;
        if (Source * const* v = std::get_if<Source *>(&target)) {
            MediaSource *ms = dynamic_cast<MediaSource*>(*v);
            if (ms) 
                duration = ms->mediaplayer()->timeline()->duration();
        }

        static bool valid = false;
        guint64 target_time = edited->value();
        if ( ImGuiToolkit::InputTime("##CALLBACK_SEEK", &target_time, duration, &valid) ){
            if (valid)
                edited->setValue( target_time );
        }

        ImGui::SameLine(0, IMGUI_SAME_LINE / 3);
        testButton(edited, target, "Target time (HH:MM:SS.MS) to set where to jump to in a video source.", 15, 7);
    }
        break;

    case SourceCallback::CALLBACK_FLAG:
    {
        Flag *edited = static_cast<Flag*>(callback);

        ImGuiToolkit::Indication(press_tooltip[0], 2, 13);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);

        int max = -1;
        if (Source * const* v = std::get_if<Source *>(&target)) {
            MediaSource *ms = dynamic_cast<MediaSource*>(*v);
            if (ms) 
                max = ms->mediaplayer()->timeline()->numFlags() - 1;
        }
        int val = MIN( (int) edited->value(), max);

        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderInt("##CALLBACK_PLAY_FLAG", &val, -1, max, val < 0 ?  ICON_FA_FLAG " Next" : ICON_FA_FLAG " %d"))
            edited->setValue(val );

        ImGui::SameLine(0, IMGUI_SAME_LINE / 3);
        testButton(edited, target, "Flag to jump to in a video source.", 11, 6);

    }
        break;

    case SourceCallback::CALLBACK_BRIGHTNESS:
    {
        SetBrightness *edited = static_cast<SetBrightness*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_BRIGHTNESS", &val, -1.f, 1.f))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Brightness for color correction.", 5, 16);
    }
        break;

    case SourceCallback::CALLBACK_CONTRAST:
    {
        SetContrast *edited = static_cast<SetContrast*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_CONTRAST", &val, -1.f, 1.f))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Contrast for color correction.", 5, 16);
    }
        break;

    case SourceCallback::CALLBACK_SATURATION:
    {
        SetSaturation *edited = static_cast<SetSaturation*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_SATURATION", &val, -1.f, 1.f))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Saturation for color correction.", 9, 16);
    }
        break;

    case SourceCallback::CALLBACK_HUE:
    {
        SetHue *edited = static_cast<SetHue*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_HUE", &val, 0.f, 1.f))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Hue shift for color correction.", 3, 4);
    }
        break;

    case SourceCallback::CALLBACK_THRESHOLD:
    {
        SetThreshold *edited = static_cast<SetThreshold*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        float val = edited->value();
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_THRESHOLD", &val, 0.0, 1.0, val < 0.001 ? "None" : "%.2f"))
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 3);
        testButton(edited, target, "Threshold for color correction.", 5, 4);
    }
        break;

    case SourceCallback::CALLBACK_GAMMA:
    {
        SetGamma *edited = static_cast<SetGamma*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);

        ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration()));
        int speed_index = d.index;
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip ))
            edited->setDuration(speed_values[speed_index]);

        glm::vec4 val = edited->value();
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::ColorEdit3("##CALLBACK_GAMMA Color", glm::value_ptr(val), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) )
            edited->setValue(val);
        ImGui::SetNextItemWidth(right_align);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        if (ImGui::SliderFloat("##CALLBACK_GAMMA Gamma", &val.w, 0.5f, 10.f, "%.2f", 2.f) )
            edited->setValue(val);

        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Set Gamma color correction.", 6, 4);
    }
        break;

    case SourceCallback::CALLBACK_INVERT:
    {
        SetInvert *edited = static_cast<SetInvert*>(callback);

        bool bd = edited->bidirectional();
        if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) )
            edited->setBidirectional(bd);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);

        int val = (int) edited->value();
        ImGui::SetNextItemWidth(right_align);
        if (ImGui::Combo("##CALLBACK_INVERT", &val, "None\0Color RGB\0Luminance\0"))
            edited->setValue( (float) val);
        ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
        testButton(edited, target, "Invert mode for color correction.", 4, 4);
    }
        break;

    default:
        break;
    }
}


void InputMappingWindow::Render()
{
    const ImGuiContext& g = *GImGui;
    const ImVec2 keyItemSpacing = ImVec2(g.FontSize * 0.2f, g.FontSize * 0.2f);
    const ImVec2 keyLetterIconSize = ImVec2(g.FontSize * 1.9f, g.FontSize * 1.9f);
    const ImVec2 keyLetterItemSize = keyLetterIconSize + keyItemSpacing;
    const ImVec2 keyNumpadIconSize = ImVec2(g.FontSize * 2.4f, g.FontSize * 2.4f);
    const ImVec2 keyNumpadItemSize = keyNumpadIconSize + keyItemSpacing;
    const float  fixed_height = keyLetterItemSize.y * 5.f + g.Style.WindowBorderSize + g.FontSize + g.Style.FramePadding.y * 2.0f + keyItemSpacing.y;
    const float  inputarea_width = keyLetterItemSize.x * 5.f;

    ImGui::SetNextWindowPos(ImVec2(530, 600), ImGuiCond_FirstUseEver);
    ImGui::SetNextWindowSize(ImVec2(1000, fixed_height), ImGuiCond_FirstUseEver);
    ImGui::SetNextWindowSizeConstraints(ImVec2(900, fixed_height), ImVec2(FLT_MAX, fixed_height));

    if ( !ImGui::Begin(name_, &Settings::application.widget.inputs,
                       ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar |  ImGuiWindowFlags_NoCollapse ))
    {
        ImGui::End();
        return;
    }

    // short variable to refer to session
    Session *S = Mixer::manager().session();

    // menu (no title bar)
    if (ImGui::BeginMenuBar())
    {
        // Close and widget menu
        if (ImGuiToolkit::IconButton(4,16))
            Settings::application.widget.inputs = false;
        if (ImGui::BeginMenu(IMGUI_TITLE_INPUT_MAPPING))
        {
            // Disable
            ImGui::MenuItem( ICON_FA_BAN "  Disable", nullptr, &Settings::application.mapping.disabled);

            // Clear all
            if ( ImGui::MenuItem( ICON_FA_BACKSPACE " Clear all" ) )
                S->clearInputCallbacks();

            // output manager menu
            ImGui::Separator();
            bool pinned = Settings::application.widget.inputs_view == Settings::application.current_view;
            std::string menutext = std::string( ICON_FA_MAP_PIN "    Stick to ") + Settings::application.views[Settings::application.current_view].name + " view";
            if ( ImGui::MenuItem( menutext.c_str(), nullptr, &pinned) ){
                if (pinned)
                    Settings::application.widget.inputs_view = Settings::application.current_view;
                else
                    Settings::application.widget.inputs_view = -1;
            }
            if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_INPUTS) )
                Settings::application.widget.inputs = false;

            ImGui::EndMenu();
        }

        // Selection of the keyboard mode
        if (ImGui::BeginMenu( input_mode[Settings::application.mapping.mode].c_str() ))
        {
            for (size_t i = 0; i < input_mode.size(); ++i) {
                if (ImGui::MenuItem(input_mode[i].c_str())) {
                    current_input_for_mode[Settings::application.mapping.mode] = current_input_;
                    Settings::application.mapping.mode = i;
                    current_input_ = current_input_for_mode[i];
                }
            }
            ImGui::EndMenu();
        }

        // Options for current key
        const std::string key = (current_input_ < INPUT_NUMPAD_LAST) ? "  Key " : "  ";
        const std::string keymenu = ICON_FA_ARROW_RIGHT + key + Control::manager().inputLabel(current_input_);
        if (ImGui::BeginMenu(keymenu.c_str()) )
        {
            if ( ImGui::MenuItem(ICON_FA_TIMES "  Reset", NULL, false, S->inputAssigned(current_input_) ) )
                // remove all source callback of this input
                S->deleteInputCallbacks(current_input_);

            if (ImGuiToolkit::BeginMenuIcon(4, 13, "Metronome",
                                 S->inputAssigned(current_input_) && Settings::application.mapping.mode < 4 ))
            {
                Metronome::Synchronicity sync = S->inputSynchrony(current_input_);
                bool active = sync == Metronome::SYNC_NONE;
                if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", NULL, active )){
                    S->setInputSynchrony(current_input_, Metronome::SYNC_NONE);
                }
                active = sync == Metronome::SYNC_BEAT;
                if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active )){
                    S->setInputSynchrony(current_input_, Metronome::SYNC_BEAT);
                }
                active = sync == Metronome::SYNC_PHASE;
                if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active )){
                    S->setInputSynchrony(current_input_, Metronome::SYNC_PHASE);
                }
                ImGui::EndMenu();
            }

            std::list<uint> models = S->assignedInputs();
            if (models.empty())
                ImGui::TextDisabled(ICON_FA_COPY "  Copy from");
            else {
                if (ImGui::BeginMenu(ICON_FA_COPY "  Copy from", models.size() > 0) )
                {
                    for (auto m = models.cbegin(); m != models.cend(); ++m) {
                        if ( *m != current_input_ )   {
                            if ( ImGui::MenuItem( Control::inputLabel( *m ).c_str() ) ){
                                S->copyInputCallback( *m, current_input_);
                            }
                        }
                    }
                    ImGui::EndMenu();
                }
            }
            ImGui::EndMenu();
        }
        ImGui::EndMenuBar();
    }

    // current window draw parameters
    const ImGuiWindow* window = ImGui::GetCurrentWindow();
    ImDrawList* draw_list = window->DrawList;
    ImVec2 frame_top = ImGui::GetCursorScreenPos();

    // change mode if a key is pressed
    for (uint k = INPUT_KEYBOARD_FIRST; k < INPUT_TIMER_FIRST; ++k) {
        if (Control::manager().inputActive(k)) {
            if (k < INPUT_NUMPAD_FIRST)
                Settings::application.mapping.mode = 0;
            else if (k < INPUT_JOYSTICK_FIRST)
                Settings::application.mapping.mode = 1;
            else if (k > INPUT_JOYSTICK_LAST_AXIS)
                Settings::application.mapping.mode = 2;
            else if (k < INPUT_JOYSTICK_FIRST_AXIS)
                Settings::application.mapping.mode = 3;
        }
    }

    //
    // KEYBOARD
    //
    if ( Settings::application.mapping.mode == 0 ) {

        // Draw table of letter keys [A] to [Y]
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
        ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.50f));
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing);
        ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
        color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
        ImGui::PushStyleColor(ImGuiCol_Header, color);
        color = ImGui::GetStyle().Colors[ImGuiCol_Text];
        color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
        ImGui::PushStyleColor(ImGuiCol_Text, color);
        ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));

        for (uint ik = INPUT_KEYBOARD_FIRST; ik < INPUT_KEYBOARD_LAST; ++ik){
            int i = ik - INPUT_KEYBOARD_FIRST;
            // draw overlay on active keys
            if ( Control::manager().inputActive(ik) ) {
                ImVec2 pos = frame_top + keyLetterItemSize * ImVec2(
                    static_cast<float>(i % 5),
                    floor(i / 5)
                );
                draw_list->AddRectFilled(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f);
                // set current
                current_input_ = ik;
            }
            // draw key button
            ImGui::PushID(ik);
            if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), S->inputAssigned(ik), 0, keyLetterIconSize)) {
                current_input_ = ik;
                // TODO SET VAR current input assigned??
            }
            ImGui::PopID();

            // if user clics and drags an assigned key icon...
            if (S->inputAssigned(ik) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
                ImGui::SetDragDropPayload("DND_KEYBOARD", &ik, sizeof(uint));
                ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ik).c_str());
                ImGui::EndDragDropSource();
            }
            // ...and drops it onto another key icon
            if (ImGui::BeginDragDropTarget()) {
                if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_KEYBOARD")) {
                    if ( payload->DataSize == sizeof(uint) ) {
                        // drop means change key of input callbacks
                        uint previous_input_key = *(const int*)payload->Data;
                        // swap
                        S->swapInputCallback(previous_input_key, ik);
                        // switch to this key
                        current_input_ = ik;
                    }
                }
                ImGui::EndDragDropTarget();
            }

            // 5 elements in a row
            if ((i % 5) < 4) ImGui::SameLine();

            // Draw frame
            ImVec2 pos = frame_top + keyLetterItemSize * ImVec2(
                static_cast<float>(i % 5),
                floor(i / 5)
            );
            if (ik == current_input_)
                draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);
            else
                draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f);

        }
        ImGui::PopStyleColor(3);
        ImGui::PopStyleVar(2);
        ImGui::PopFont();

    }
    //
    // NUMPAD
    //
    else if ( Settings::application.mapping.mode == 1 ) {

        // custom layout of numerical keypad
        std::vector<uint> numpad_inputs = { INPUT_NUMPAD_FIRST+7, INPUT_NUMPAD_FIRST+8, INPUT_NUMPAD_FIRST+9, INPUT_NUMPAD_FIRST+11,
                                            INPUT_NUMPAD_FIRST+4, INPUT_NUMPAD_FIRST+5, INPUT_NUMPAD_FIRST+6, INPUT_NUMPAD_FIRST+12,
                                            INPUT_NUMPAD_FIRST+1, INPUT_NUMPAD_FIRST+2, INPUT_NUMPAD_FIRST+3, INPUT_NUMPAD_FIRST+13,
                                            INPUT_NUMPAD_FIRST+0, INPUT_NUMPAD_FIRST+10, INPUT_NUMPAD_FIRST+14 };

        // Draw table of keypad keys
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
        ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.50f));
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing);
        ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
        color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
        ImGui::PushStyleColor(ImGuiCol_Header, color);
        color = ImGui::GetStyle().Colors[ImGuiCol_Text];
        color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
        ImGui::PushStyleColor(ImGuiCol_Text, color);
        ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));

        for (size_t p = 0; p < numpad_inputs.size(); ++p){
            uint ik = numpad_inputs[p];
            ImVec2 iconsize = p == 12 ? keyNumpadIconSize + ImVec2(keyNumpadIconSize.x+ g.Style.ItemSpacing.x, 0.f) : keyNumpadIconSize;
            ImVec2 itemsize = p == 12 ? keyNumpadItemSize + ImVec2(keyNumpadItemSize.x+ g.Style.ItemSpacing.x, 0.f) : keyNumpadItemSize;
            // draw overlay on active keys
            if ( Control::manager().inputActive(ik) ) {
                ImVec2 pos = frame_top + itemsize * ImVec2( static_cast<float>(p % 4), floor(p / 4) );
                pos += p > 12 ? ImVec2(keyNumpadIconSize.x+ g.Style.ItemSpacing.x, 0.f) : ImVec2(0,0);
                draw_list->AddRectFilled(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Border), 6.f);
                // set current
                current_input_ = ik;
            }
            // draw key button
            ImGui::PushID(ik);
            if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), S->inputAssigned(ik), 0, iconsize)) {
                current_input_ = ik;
            }
            ImGui::PopID();
            // if user clics and drags an assigned key icon...
            if (S->inputAssigned(ik) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
                ImGui::SetDragDropPayload("DND_NUMPAD", &ik, sizeof(uint));
                ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ik).c_str());
                ImGui::EndDragDropSource();
            }
            // ...and drops it onto another key icon
            if (ImGui::BeginDragDropTarget()) {
                if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_NUMPAD")) {
                    if ( payload->DataSize == sizeof(uint) ) {
                        // drop means change key of input callbacks
                        uint previous_input_key = *(const int*)payload->Data;
                        // swap
                        S->swapInputCallback(previous_input_key, ik);
                        // switch to this key
                        current_input_ = ik;
                    }
                }
                ImGui::EndDragDropTarget();
            }

            // 4 elements in a row
            if ((p % 4) < 3) ImGui::SameLine();

            // Draw frame
            ImVec2 pos = frame_top + itemsize * ImVec2(
                static_cast<float>(p % 4),
                floor(p / 4)
            );
            pos += p > 12 ? ImVec2(keyNumpadIconSize.x + g.Style.ItemSpacing.x, 0.f) : ImVec2(0,0);
            if (ik == current_input_)
                draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);
            else
                draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f);

        }
        ImGui::PopStyleColor(3);
        ImGui::PopStyleVar(2);
        ImGui::PopFont();

    }
    //
    // MULTITOUCH OSC
    //
    else if ( Settings::application.mapping.mode == 2 ) {

        // Draw table of TouchOSC buttons
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
        ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f));
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing);
        ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
        color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
        ImGui::PushStyleColor(ImGuiCol_Header, color);
        color = ImGui::GetStyle().Colors[ImGuiCol_Text];
        color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
        ImGui::PushStyleColor(ImGuiCol_Text, color);
        ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));

        const ImVec2 touch_bar_size = keyNumpadItemSize * ImVec2(0.65f, 0.2f);
        const ImVec2 touch_bar_pos  = keyNumpadItemSize * ImVec2(0.125f, 0.6f);

        for (size_t t = 0; t < INPUT_MULTITOUCH_COUNT; ++t){
            uint it = INPUT_MULTITOUCH_FIRST + t;
            ImVec2 pos = frame_top + keyNumpadItemSize * ImVec2( static_cast<float>(t % 4), floor(t / 4) );

            // draw overlay on active keys
            if ( Control::manager().inputActive(it) ) {
                draw_list->AddRectFilled(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f);
                // set current
                current_input_ = it;
            }

            // draw key button
            ImGui::PushID(it);
            if (ImGui::Selectable(" ", S->inputAssigned(it), 0, keyNumpadIconSize))
                current_input_ = it;
            ImGui::PopID();

            // if user clics and drags an assigned key icon...
            if (S->inputAssigned(it) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
                ImGui::SetDragDropPayload("DND_MULTITOUCH", &it, sizeof(uint));
                ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(it).c_str());
                ImGui::EndDragDropSource();
            }
            // ...and drops it onto another key icon
            if (ImGui::BeginDragDropTarget()) {
                if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_MULTITOUCH")) {
                    if ( payload->DataSize == sizeof(uint) ) {
                        // drop means change key of input callbacks
                        uint previous_input_key = *(const int*)payload->Data;
                        // swap
                        S->swapInputCallback(previous_input_key, it);
                        // switch to this key
                        current_input_ = it;
                    }
                }
                ImGui::EndDragDropTarget();
            }

            // 4 elements in a row
            if ((t % 4) < 3) ImGui::SameLine();

            // Draw frame
            if (it == current_input_)
                draw_list->AddRect(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);
            else
                draw_list->AddRect(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f);

            // Draw value bar
            ImVec2 prev = ImGui::GetCursorScreenPos();
            ImGui::SetCursorScreenPos( pos + touch_bar_pos);
            ImGui::ProgressBar(Control::manager().inputValue(it), touch_bar_size, "");
            ImGui::SetCursorScreenPos( prev );

        }

        ImGui::PopStyleColor(3);
        ImGui::PopStyleVar(2);
        ImGui::PopFont();

    }
    //
    // JOYSTICK
    //
    else if ( Settings::application.mapping.mode == 3 ) {

        // custom layout of gamepad buttons
        std::vector<uint> gamepad_inputs = { INPUT_JOYSTICK_FIRST_BUTTON+11, INPUT_JOYSTICK_FIRST_BUTTON+13,
                                             INPUT_JOYSTICK_FIRST_BUTTON+6,
                                             INPUT_JOYSTICK_FIRST_BUTTON+2, INPUT_JOYSTICK_FIRST_BUTTON+3,

                                             INPUT_JOYSTICK_FIRST_BUTTON+14, INPUT_JOYSTICK_FIRST_BUTTON+12,
                                             INPUT_JOYSTICK_FIRST_BUTTON+7,
                                             INPUT_JOYSTICK_FIRST_BUTTON+0, INPUT_JOYSTICK_FIRST_BUTTON+1,

                                             INPUT_JOYSTICK_FIRST_BUTTON+4, INPUT_JOYSTICK_FIRST_BUTTON+9,
                                             INPUT_JOYSTICK_FIRST_BUTTON+8,
                                             INPUT_JOYSTICK_FIRST_BUTTON+10, INPUT_JOYSTICK_FIRST_BUTTON+5  };

        std::vector< std::string > gamepad_labels = {  ICON_FA_ARROW_UP,  ICON_FA_ARROW_DOWN,
                                                       ICON_FA_CHEVRON_CIRCLE_LEFT, "X", "Y",
                                                       ICON_FA_ARROW_LEFT, ICON_FA_ARROW_RIGHT,
                                                       ICON_FA_CHEVRON_CIRCLE_RIGHT, "A", "B",
                                                       "L1", "LT", ICON_FA_DOT_CIRCLE, "RT", "R1" };

        // Draw table of Gamepad Buttons
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
        ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
        color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
        ImGui::PushStyleColor(ImGuiCol_Header, color);
        color = ImGui::GetStyle().Colors[ImGuiCol_Text];
        color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
        ImGui::PushStyleColor(ImGuiCol_Text, color);
        ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));

        // CENTER text for button
        ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f));
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing);
        for (size_t b = 0; b < gamepad_inputs.size(); ++b ){
            uint ig = gamepad_inputs[b];
            // draw overlay on active keys
            if ( Control::manager().inputActive(ig) ) {
                ImVec2 pos = frame_top + keyLetterItemSize * ImVec2(
                    static_cast<float>(b % 5),
                    floor(b / 5)
                );
                draw_list->AddRectFilled(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f);
                // set current
                current_input_ = ig;
            }
            // draw key button
            ImGui::PushID(ig);
            if (ImGui::Selectable(gamepad_labels[b].c_str(), S->inputAssigned(ig), 0, keyLetterIconSize))
                current_input_ = ig;
            ImGui::PopID();

            // if user clics and drags an assigned key icon...
            if (S->inputAssigned(ig) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
                ImGui::SetDragDropPayload("DND_GAMEPAD", &ig, sizeof(uint));
                ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ig).c_str());
                ImGui::EndDragDropSource();
            }
            // ...and drops it onto another key icon
            if (ImGui::BeginDragDropTarget()) {
                if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_GAMEPAD")) {
                    if ( payload->DataSize == sizeof(uint) ) {
                        // drop means change key of input callbacks
                        uint previous_input_key = *(const int*)payload->Data;
                        // swap
                        S->swapInputCallback(previous_input_key, ig);
                        // switch to this key
                        current_input_ = ig;
                    }
                }
                ImGui::EndDragDropTarget();
            }

            if ((b % 5) < 4) ImGui::SameLine();

            // Draw frame
            ImVec2 pos = frame_top + keyLetterItemSize * ImVec2(
                static_cast<float>(b % 5),
                floor(b / 5)
            );
            if (ig == current_input_)
                draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);
            else if ( b!= 2 && b!= 7 && b!=12 )
                draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f);

        }
        ImGui::PopStyleVar();
        ImGui::PopFont();

        // Table of Gamepad Axis
        const ImVec2 axis_top = frame_top + ImVec2(0.f, 3.f * keyLetterItemSize.y);
        const ImVec2 axis_item_size(inputarea_width / 2.f, (2.f * keyLetterItemSize.y) / 3.f);
        const ImVec2 axis_icon_size = axis_item_size - g.Style.ItemSpacing;
        const ImVec2 axis_bar_size = axis_icon_size * ImVec2(0.4f, 0.4f);
        ImVec2 axis_bar_pos(axis_icon_size.x * 0.5f, axis_icon_size.y *0.3f );

        // LEFT align for 3 axis on the left
        ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.09f, 0.5f));

        // define top left screen cursor position
        ImVec2 pos = axis_top;
        // Draw a little bar showing activity on the gamepad axis
        ImGui::SetCursorScreenPos( pos + axis_bar_pos);
        ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS), axis_bar_size);
        // Draw button to assign the axis to an action
        ImGui::SetCursorScreenPos( pos );
        if (ImGui::Selectable("LX", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS), 0, axis_icon_size))
            current_input_ = INPUT_JOYSTICK_FIRST_AXIS;
        // Draw frame around current gamepad axis
        if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS)
            draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);

        pos = axis_top + ImVec2( 0, axis_item_size.y);
        ImGui::SetCursorScreenPos( pos + axis_bar_pos);
        ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+1), axis_bar_size);
        ImGui::SetCursorScreenPos( pos );
        if (ImGui::Selectable("LY", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+1), 0, axis_icon_size))
            current_input_ = INPUT_JOYSTICK_FIRST_AXIS+1;
        if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+1)
            draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);

        pos = axis_top + ImVec2( 0, 2.f * axis_item_size.y);
        ImGui::SetCursorScreenPos( pos + axis_bar_pos);
        ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+2), axis_bar_size);
        ImGui::SetCursorScreenPos( pos );
        if (ImGui::Selectable("L2", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+2), 0, axis_icon_size))
            current_input_ = INPUT_JOYSTICK_FIRST_AXIS+2;
        if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+2)
            draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);

        ImGui::PopStyleVar();

        // RIGHT align for 3 axis on the right
        ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.91f, 0.5f));
        axis_bar_pos.x = g.Style.ItemSpacing.x;

        pos = axis_top + ImVec2( axis_item_size.x, 0.f);
        ImGui::SetCursorScreenPos( pos + axis_bar_pos);
        ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+3), axis_bar_size);
        ImGui::SetCursorScreenPos( pos );
        if (ImGui::Selectable("RX", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+3), 0, axis_icon_size))
            current_input_ = INPUT_JOYSTICK_FIRST_AXIS+3;
        if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+3)
            draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);

        pos = axis_top + ImVec2( axis_item_size.x, axis_item_size.y);
        ImGui::SetCursorScreenPos( pos + axis_bar_pos);
        ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+4), axis_bar_size);
        ImGui::SetCursorScreenPos( pos );
        if (ImGui::Selectable("RY", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+4), 0, axis_icon_size))
            current_input_ = INPUT_JOYSTICK_FIRST_AXIS+4;
        if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+4)
            draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);

        pos = axis_top + ImVec2( axis_item_size.x, 2.f * axis_item_size.y);
        ImGui::SetCursorScreenPos( pos + axis_bar_pos);
        ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+5), axis_bar_size);
        ImGui::SetCursorScreenPos( pos );
        if (ImGui::Selectable("R2", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+5), 0, axis_icon_size))
            current_input_ = INPUT_JOYSTICK_FIRST_AXIS+5;
        if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+5)
            draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);

        ImGui::PopStyleVar(2);

        // Done with color change
        ImGui::PopStyleColor(3);

    }
    //
    // TIMER
    //
    else if ( Settings::application.mapping.mode == 4 ) {

        ImGuiIO& io = ImGui::GetIO();
        const ImVec2 circle_center = frame_top + (ImVec2(inputarea_width, inputarea_width) )/ 2.f;
        const float circle_radius = (inputarea_width - IMGUI_SAME_LINE) / 2.f;
        const glm::vec2 mpo = glm::vec2 (io.MousePos.x - circle_center.x, io.MousePos.y - circle_center.y);
        const float angle = - glm::orientedAngle( glm::normalize(mpo), glm::vec2(1.f,0.f));
        const float lenght = glm::length(mpo);
        const float cm = 0.03f ; // circle margin

        // color palette
        ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
        color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
        ImGui::PushStyleColor(ImGuiCol_Header, color);
        color = ImGui::GetStyle().Colors[ImGuiCol_Text];
        color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
        ImGui::PushStyleColor(ImGuiCol_Text, color);
        ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));

        // draw background ring
        static ImU32 colorbg = ImGui::GetColorU32(ImGuiCol_FrameBgActive, 0.6f);
        draw_list->AddCircleFilled(circle_center, circle_radius, colorbg, PLOT_CIRCLE_SEGMENTS);

        // metronome timer info
        char text_buf[24] = {0};
        const double q = Metronome::manager().quantum();
        static const float resolution = PLOT_CIRCLE_SEGMENTS / (2.f * M_PI);
        static ImVec2 buffer[PLOT_CIRCLE_SEGMENTS];

        // draw all slices of metronome ring
        for (uint ip = 0 ; ip < (uint) floor(q) ; ++ip) {
            float a0 = cm - M_PI_2 + (float(ip) * (2.f * M_PI)) / floor(q);
            float a1 = (-2.f * cm) + a0 + (2.f * M_PI) / floor(q);
            int n = ImMax(3, (int)((a1 - a0) * resolution));
            double da = (a1 - a0) / (n - 1);
            int index = 0;
            // start drawing at center point of slice
            float a01 =  (a0 + a1) / 2.f;
            buffer[index++] = ImVec2(circle_center.x + cm * circle_radius * cos(a01), circle_center.y + cm * circle_radius * sin(a01));
            // draw round external border of slice
            for (int i = 0; i < n; ++i) {
                double a = a0 + i * da;
                buffer[index++] = ImVec2(circle_center.x + circle_radius * cos(a), circle_center.y + circle_radius * sin(a));
            }
            // Test mouse over in slices of the circle
            // 1) test if mouse is inside area
            if (ImGui::IsMouseHoveringRect(frame_top, frame_top + ImVec2(inputarea_width, inputarea_width), true))
            {
                // 2) test angle of mouse coordinate in circle
                if (lenght < circle_radius && ( ( angle > a0 && angle < a1) || (angle + (2.f * M_PI) > a0 && angle + (2.f * M_PI) < a1) ) )
                {
                    // draw the mouse-over slice
                    draw_list->AddConvexPolyFilled(buffer, index, ImGui::GetColorU32(ImGuiCol_HeaderHovered));
                    // indicate tempo of mouse-over slice
                    snprintf(text_buf, 24, "%d/%d", ip + 1, (int) floor(q) );

                    // mouse clic
                    if (ImGui::IsMouseClicked(0)) {
                        current_input_ = ip + INPUT_TIMER_FIRST;
                    }
                }
            }

            // draw the slice showing its assigned in this session
            if (S->inputAssigned(ip + INPUT_TIMER_FIRST))
                draw_list->AddConvexPolyFilled(buffer, index, ImGui::GetColorU32(ImGuiCol_Header));

            // draw the border of the slice
            if (ip + INPUT_TIMER_FIRST == current_input_)
                // current active slice has bold white border
                draw_list->AddPolyline(buffer, index, ImGui::GetColorU32(ImGuiCol_Text), true, 3.f);
            else
                draw_list->AddPolyline(buffer, index, ImGui::GetColorU32(ImGuiCol_Button), true, 0.5f);

        }

        // draw clock hand
        float a = -M_PI_2 + (Metronome::manager().phase()/q) * (2.f * M_PI);
        draw_list->AddLine(ImVec2(circle_center.x + cos(a), circle_center.y + sin(a)),
                           ImVec2(circle_center.x + circle_radius * cos(a), circle_center.y + circle_radius * sin(a)),
                           ImGui::GetColorU32(ImGuiCol_PlotHistogram), 2.f);

        // display text indication 'x / N' on mouse over in a central circle
        draw_list->AddCircleFilled(circle_center, circle_radius * 0.25f, ImGui::GetColorU32(ImGuiCol_Button, 10.f), PLOT_CIRCLE_SEGMENTS);
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
        ImVec2 label_size = ImGui::CalcTextSize(text_buf, NULL);
        ImGui::SetCursorScreenPos(circle_center - label_size/2);
        ImGui::Text("%s", text_buf);
        ImGui::PopFont();

        ImGui::PopStyleColor(3);
    }

    // Draw child Window (right) to list reactions to input
    ImGui::SetCursorScreenPos(frame_top + g.Style.FramePadding + ImVec2(inputarea_width,0.f));
    {
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2.f, g.Style.ItemSpacing.y * 2.f) );
        ImGui::BeginChild("InputsMappingInterfacePanel", ImVec2(0, 0), false);
        float w = ImGui::GetWindowWidth() *0.25f;

        // adding actions is possible only if there are sources in the session
        if (!Mixer::manager().session()->empty()) {

            static bool temp_new_input = false;
            static Target temp_new_target;
            static uint temp_new_callback = 0;

            ///
            /// list of input callbacks for the current input
            ///
            if (S->inputAssigned(current_input_)) {

                auto result = S->getSourceCallbacks(current_input_);
                for (auto kit = result.cbegin(); kit != result.cend(); ++kit) {

                    Target target = kit->first;
                    SourceCallback *callback = kit->second;

                    // push ID because we repeat the same UI
                    ImGui::PushID( (void*) callback);

                    // Delete interface
                    if (ImGuiToolkit::IconButton(ICON_FA_MINUS, "Remove") ){
                        S->deleteInputCallback(callback);
                        // reload
                        ImGui::PopID();
                        break;
                    }

                    // Select Target
                    ImGui::SameLine(0, IMGUI_SAME_LINE);
                    ImGui::SetNextItemWidth(w);
                    Target selected_target = ComboSelectTarget(target);
                    // if the selected target variant was filled with a value
                    if (selected_target.index() > 0) {
                        // reassign the callback to newly selected source
                        S->assignInputCallback(current_input_, selected_target, callback);
                        // reload
                        ImGui::PopID();
                        break;
                    }

                    // check if target is a Source with image processing enabled
                    bool ismediaplayer = false;
                    bool withimageprocessing = false;
                    if ( target.index() == 1 ) {
                        if (Source * const* v = std::get_if<Source *>(&target)) {
                            withimageprocessing = (*v)->imageProcessingEnabled();
                            ismediaplayer = dynamic_cast<MediaSource*>(*v) != nullptr;
                        }
                    }

                    // Select Reaction
                    ImGui::SameLine(0, IMGUI_SAME_LINE);
                    ImGui::SetNextItemWidth(w);
                    uint type = ComboSelectCallback( callback->type(), withimageprocessing, ismediaplayer );
                    if (type == SourceCallback::CALLBACK_INVALID) {
                        // remove previous callback
                        S->deleteInputCallback(callback);   
                        // offer to create a new one instead       
                        temp_new_input = true;
                        temp_new_target = target;                        
                        // reload
                        ImGui::PopID();
                        break;
                    }
                    if (type > 0) {
                        // remove previous callback
                        S->deleteInputCallback(callback);
                        // assign callback
                        S->assignInputCallback(current_input_, target, SourceCallback::create((SourceCallback::CallbackType)type) );
                        // reload
                        ImGui::PopID();
                        break;
                    }

                    // Adjust parameters
                    ImGui::SameLine(0, IMGUI_SAME_LINE);
                    if (callback)
                        SliderParametersCallback( callback, target );

                    ImGui::PopID();

                }

            }
            else {
                ImGui::Text("No action mapped to this input. Add one with +.");
            }

            ///
            /// Add a new interface
            ///
            ///

            // step 1 : press '+'
            if (temp_new_input) {
                if (ImGuiToolkit::IconButton(ICON_FA_TIMES, "Cancel") ){
                    temp_new_target = Uninitialized{};
                    temp_new_callback = 0;
                    temp_new_input = false;
                }
                ImGui::SameLine(0, IMGUI_SAME_LINE + ImGui::GetFontSize() * 0.15f);
            }
            else {
                if (ImGuiToolkit::IconButton(ICON_FA_PLUS, "Add mapping") )
                    temp_new_input = true;
                ImGui::SameLine(0, IMGUI_SAME_LINE);
            }

            if (temp_new_input) {
                // step 2 : Get input for source
                ImGui::SetNextItemWidth(w);

                Target selected_target = ComboSelectTarget(temp_new_target);
                // if the selected target variant was filled with a value
                if (selected_target.index() > 0) {
                    temp_new_target = selected_target;
                    temp_new_callback = 0;
                }
                // possible new target
                if (temp_new_target.index() > 0) {
                    // check if target is a Source with image processing enabled
                    bool mediaplayer = false;
                    bool withimageprocessing = false;
                    if ( temp_new_target.index() == 1 ) {
                        if (Source * const* v = std::get_if<Source *>(&temp_new_target)) {
                            withimageprocessing = (*v)->imageProcessingEnabled();
                            mediaplayer = dynamic_cast<MediaSource*>(*v) != nullptr;
                        }
                    }
                    // step 3: Get input for callback type
                    ImGui::SameLine(0, IMGUI_SAME_LINE);
                    ImGui::SetNextItemWidth(w);
                    temp_new_callback = ComboSelectCallback( temp_new_callback, withimageprocessing, mediaplayer );
                    // user selected a callback type
                    if (temp_new_callback > 0 && temp_new_callback != SourceCallback::CALLBACK_INVALID) {
                        // step 4 : create new callback and add it to source
                        S->assignInputCallback(current_input_, temp_new_target, SourceCallback::create((SourceCallback::CallbackType)temp_new_callback) );
                        // done
                        temp_new_target = Uninitialized{};
                        temp_new_callback = 0;
                        temp_new_input = false;
                    }
                }
            }
        }
        else {
            ImGui::Text("No source to perform an action.");
        }

        ///
        /// Sync info lower right corner
        ///
        Metronome::Synchronicity sync = S->inputSynchrony(current_input_);
        if ( sync > Metronome::SYNC_NONE) {
            ImGui::SetCursorPos(ImGui::GetWindowSize() - ImVec2(50, 50));
            ImGuiToolkit::Icon( sync > Metronome::SYNC_BEAT ? 7 : 6, 13);
        }

        // close child window
        ImGui::EndChild();
        ImGui::PopStyleVar();
    }


    ImGui::End();
}

