////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////


// This is a minimal integration example. For simplification purposes only basic input events are
// handled, no resource providers are used and the shutdown procedure is omitted. A more complete
// multiplatform integration sample with step by step comments can be found at 'Samples/Integration'


#ifdef _WIN32
#include "glut.h"
#pragma comment(linker,"/SUBSYSTEM:CONSOLE")
#endif

#ifdef __APPLE__
#include <GLUT/glut.h>
#endif

#ifdef __EMSCRIPTEN__
#include <GL/glut.h>
#include <GLES3/gl32.h>
#include <emscripten/html5.h>
#endif

#ifdef __linux__
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glut.h>
#endif

#ifdef _MSC_VER
#define UNUSED_ARGS(...) (void)(true ? (void)0 : ((void)(__VA_ARGS__)))
#else
#define UNUSED_ARGS(...)
#endif


#include <NsApp/ThemeProviders.h>
#include <NsRender/GLFactory.h>
#include <NsGui/FontProperties.h>
#include <NsGui/IntegrationAPI.h>
#include <NsGui/IRenderer.h>
#include <NsGui/IView.h>
#include <NsGui/Grid.h>
#include <NsCore/HighResTimer.h>
#include <NSGui/TextBox.h>

#include <NoesisPCH.h>

#include "TSF.h"

#include <string>
#include <list>

Noesis::Key ConvertCharKeyToNoesisKey(unsigned char key)
{
    if (key == 127) //GLUT treats delete key as char key, this is fixed keycode
    {
        return Noesis::Key::Key_Delete;
    }

    short scanKey = VkKeyScanA(key);

    Noesis::Key noesisKey = Noesis::Key_None;
    switch (scanKey)
    {
    case VK_BACK: noesisKey = Noesis::Key::Key_Back; break;
    case VK_TAB: noesisKey = Noesis::Key::Key_Tab; break;
    case VK_CLEAR: noesisKey = Noesis::Key::Key_Clear; break;
    case VK_RETURN: noesisKey = Noesis::Key::Key_Return; break;
    case VK_PAUSE: noesisKey = Noesis::Key::Key_Pause; break;
    case VK_SHIFT: noesisKey = Noesis::Key::Key_LeftShift; break;
    case VK_LSHIFT: noesisKey = Noesis::Key::Key_LeftShift; break;
    case VK_RSHIFT: noesisKey = Noesis::Key::Key_RightShift; break;
    case VK_CONTROL: noesisKey = Noesis::Key::Key_LeftCtrl; break;
    case VK_LCONTROL: noesisKey = Noesis::Key::Key_LeftCtrl; break;
    case VK_RCONTROL: noesisKey = Noesis::Key::Key_RightCtrl; break;
    case VK_MENU: noesisKey = Noesis::Key::Key_LeftAlt; break;
    case VK_LMENU: noesisKey = Noesis::Key::Key_LeftAlt; break;
    case VK_RMENU: noesisKey = Noesis::Key::Key_RightAlt; break;
    case VK_LWIN: noesisKey = Noesis::Key::Key_LWin; break;
    case VK_RWIN: noesisKey = Noesis::Key::Key_RWin; break;
    case VK_ESCAPE: noesisKey = Noesis::Key::Key_Escape; break;
    case VK_SPACE: noesisKey = Noesis::Key::Key_Space; break;
    case VK_PRIOR: noesisKey = Noesis::Key::Key_Prior; break;
    case VK_NEXT: noesisKey = Noesis::Key::Key_Next; break;
    case VK_END: noesisKey = Noesis::Key::Key_End; break;
    case VK_HOME: noesisKey = Noesis::Key::Key_Home; break;
    case VK_LEFT: noesisKey = Noesis::Key::Key_Left; break;
    case VK_UP: noesisKey = Noesis::Key::Key_Up; break;
    case VK_RIGHT: noesisKey = Noesis::Key::Key_Right; break;
    case VK_DOWN: noesisKey = Noesis::Key::Key_Down; break;
    case VK_SELECT: noesisKey = Noesis::Key::Key_Select; break;
    case VK_PRINT: noesisKey = Noesis::Key::Key_Print; break;
    case VK_EXECUTE: noesisKey = Noesis::Key::Key_Execute; break;
    case VK_SNAPSHOT: noesisKey = Noesis::Key::Key_Snapshot; break;
    case VK_INSERT: noesisKey = Noesis::Key::Key_Insert; break;
    case VK_DELETE: noesisKey = Noesis::Key::Key_Delete; break;
    case VK_HELP: noesisKey = Noesis::Key::Key_Help; break;
    case '0': noesisKey = Noesis::Key::Key_D0; break;
    case '1': noesisKey = Noesis::Key::Key_D1; break;
    case '2': noesisKey = Noesis::Key::Key_D2; break;
    case '3': noesisKey = Noesis::Key::Key_D3; break;
    case '4': noesisKey = Noesis::Key::Key_D4; break;
    case '5': noesisKey = Noesis::Key::Key_D5; break;
    case '6': noesisKey = Noesis::Key::Key_D6; break;
    case '7': noesisKey = Noesis::Key::Key_D7; break;
    case '8': noesisKey = Noesis::Key::Key_D8; break;
    case '9': noesisKey = Noesis::Key::Key_D9; break;
    case VK_NUMPAD0: noesisKey = Noesis::Key::Key_NumPad0; break;
    case VK_NUMPAD1: noesisKey = Noesis::Key::Key_NumPad1; break;
    case VK_NUMPAD2: noesisKey = Noesis::Key::Key_NumPad2; break;
    case VK_NUMPAD3: noesisKey = Noesis::Key::Key_NumPad3; break;
    case VK_NUMPAD4: noesisKey = Noesis::Key::Key_NumPad4; break;
    case VK_NUMPAD5: noesisKey = Noesis::Key::Key_NumPad5; break;
    case VK_NUMPAD6: noesisKey = Noesis::Key::Key_NumPad6; break;
    case VK_NUMPAD7: noesisKey = Noesis::Key::Key_NumPad7; break;
    case VK_NUMPAD8: noesisKey = Noesis::Key::Key_NumPad8; break;
    case VK_NUMPAD9: noesisKey = Noesis::Key::Key_NumPad9; break;
    case VK_MULTIPLY: noesisKey = Noesis::Key::Key_Multiply; break;
    case VK_ADD: noesisKey = Noesis::Key::Key_Add; break;
    case VK_SEPARATOR: noesisKey = Noesis::Key::Key_Separator; break;
    case VK_SUBTRACT: noesisKey = Noesis::Key::Key_Subtract; break;
    case VK_DECIMAL: noesisKey = Noesis::Key::Key_Decimal; break;
    case VK_DIVIDE: noesisKey = Noesis::Key::Key_Divide; break;
    case 'A': noesisKey = Noesis::Key::Key_A; break;
    case 'B': noesisKey = Noesis::Key::Key_B; break;
    case 'C': noesisKey = Noesis::Key::Key_C; break;
    case 'D': noesisKey = Noesis::Key::Key_D; break;
    case 'E': noesisKey = Noesis::Key::Key_E; break;
    case 'F': noesisKey = Noesis::Key::Key_F; break;
    case 'G': noesisKey = Noesis::Key::Key_G; break;
    case 'H': noesisKey = Noesis::Key::Key_H; break;
    case 'I': noesisKey = Noesis::Key::Key_I; break;
    case 'J': noesisKey = Noesis::Key::Key_J; break;
    case 'K': noesisKey = Noesis::Key::Key_K; break;
    case 'L': noesisKey = Noesis::Key::Key_L; break;
    case 'M': noesisKey = Noesis::Key::Key_M; break;
    case 'N': noesisKey = Noesis::Key::Key_N; break;
    case 'O': noesisKey = Noesis::Key::Key_O; break;
    case 'P': noesisKey = Noesis::Key::Key_P; break;
    case 'Q': noesisKey = Noesis::Key::Key_Q; break;
    case 'R': noesisKey = Noesis::Key::Key_R; break;
    case 'S': noesisKey = Noesis::Key::Key_S; break;
    case 'T': noesisKey = Noesis::Key::Key_T; break;
    case 'U': noesisKey = Noesis::Key::Key_U; break;
    case 'V': noesisKey = Noesis::Key::Key_V; break;
    case 'W': noesisKey = Noesis::Key::Key_W; break;
    case 'X': noesisKey = Noesis::Key::Key_X; break;
    case 'Y': noesisKey = Noesis::Key::Key_Y; break;
    case 'Z': noesisKey = Noesis::Key::Key_Z; break;
    case VK_F1: noesisKey = Noesis::Key::Key_F1; break;
    case VK_F2: noesisKey = Noesis::Key::Key_F2; break;
    case VK_F3: noesisKey = Noesis::Key::Key_F3; break;
    case VK_F4: noesisKey = Noesis::Key::Key_F4; break;
    case VK_F5: noesisKey = Noesis::Key::Key_F5; break;
    case VK_F6: noesisKey = Noesis::Key::Key_F6; break;
    case VK_F7: noesisKey = Noesis::Key::Key_F7; break;
    case VK_F8: noesisKey = Noesis::Key::Key_F8; break;
    case VK_F9: noesisKey = Noesis::Key::Key_F9; break;
    case VK_F10: noesisKey = Noesis::Key::Key_F10; break;
    case VK_F11: noesisKey = Noesis::Key::Key_F11; break;
    case VK_F12: noesisKey = Noesis::Key::Key_F12; break;
    case VK_F13: noesisKey = Noesis::Key::Key_F13; break;
    case VK_F14: noesisKey = Noesis::Key::Key_F14; break;
    case VK_F15: noesisKey = Noesis::Key::Key_F15; break;
    case VK_F16: noesisKey = Noesis::Key::Key_F16; break;
    case VK_F17: noesisKey = Noesis::Key::Key_F17; break;
    case VK_F18: noesisKey = Noesis::Key::Key_F18; break;
    case VK_F19: noesisKey = Noesis::Key::Key_F19; break;
    case VK_F20: noesisKey = Noesis::Key::Key_F20; break;
    case VK_F21: noesisKey = Noesis::Key::Key_F21; break;
    case VK_F22: noesisKey = Noesis::Key::Key_F22; break;
    case VK_F23: noesisKey = Noesis::Key::Key_F23; break;
    case VK_F24: noesisKey = Noesis::Key::Key_F24; break;
    case VK_NUMLOCK: noesisKey = Noesis::Key::Key_NumLock; break;
    case VK_SCROLL: noesisKey = Noesis::Key::Key_Scroll;
    }
    return noesisKey;
}

static Noesis::Ptr<Noesis::RenderDevice> _device;
static std::list<Noesis::IView*> _views;


///////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisInit()
{
    // A logging handler is installed here. You can also install a custom error handler and memory
    // allocator. By default errors are redirected to the logging handler
    Noesis::GUI::SetLogHandler([](const char*, uint32_t, uint32_t level, const char*, const char* msg)
        {
            // [TRACE] [DEBUG] [INFO] [WARNING] [ERROR]
            const char* prefixes[] = { "T", "D", "I", "W", "E" };
    printf("[NOESIS/%s] %s\n", prefixes[level], msg);
        });

    // https://www.noesisengine.com/docs/Gui.Core.Licensing.html
    Noesis::GUI::SetLicense("test", "WOLw8pOp8PLHfabI2XuOBX26rFYjT94cw/+zrr0PBMP7Ve45");

    // Noesis initialization. This must be the first step before using any NoesisGUI functionality
    Noesis::GUI::Init();

    _device = NoesisApp::GLFactory::CreateDevice(false);

    // Setup theme
    NoesisApp::SetThemeProviders();
    Noesis::GUI::LoadApplicationResources("Theme/NoesisTheme.DarkBlue.xaml");
}

static void CreateBackground()
{
    // For simplicity purposes we are not using resource providers in this sample. ParseXaml() is
    // enough if there is no extra XAML dependencies
    Noesis::Ptr<Noesis::Grid> xaml(Noesis::GUI::ParseXaml<Noesis::Grid>(R"(<?xml version="1.0" encoding="UTF-8"?>
<Grid 
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
	xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
	xmlns:noesis="clr-namespace:NoesisGUIExtensions;assembly=Noesis.GUI.Extensions">

    <Grid Grid.Column="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="54"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="11"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Button Grid.Column="1" Grid.Row="1" Content="Test" />
    </Grid>
</Grid>
    )"));

    // View creation to render and interact with the user interface
    // We transfer the ownership to a global pointer instead of a Ptr<> because there is no way
    // in GLUT to do shutdown and we don't want the Ptr<> to be released at global time
    Noesis::IView* view = Noesis::GUI::CreateView(xaml).GiveOwnership();
    view->SetFlags(Noesis::RenderFlags_PPAA | Noesis::RenderFlags_LCD);
    view->SetSize(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));

    // Renderer initialization with an OpenGL device
    view->GetRenderer()->Init(_device);

    _views.push_back(view);
}

static void CreateChatFrame()
{
    // For simplicity purposes we are not using resource providers in this sample. ParseXaml() is
    // enough if there is no extra XAML dependencies
    Noesis::Ptr<Noesis::Grid> xaml(Noesis::GUI::ParseXaml<Noesis::Grid>(R"(<?xml version="1.0" encoding="UTF-8"?>
<Grid 
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
	xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
	xmlns:noesis="clr-namespace:NoesisGUIExtensions;assembly=Noesis.GUI.Extensions">

    <Grid Grid.Column="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        
        <TextBox Grid.Row="1" x:Name="ChatInput" Text="WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW" />
        <TextBox Grid.Row="2" x:Name="ChatSecond" Text="123123" />

    </Grid>
</Grid>
    )"));

    // View creation to render and interact with the user interface
    // We transfer the ownership to a global pointer instead of a Ptr<> because there is no way
    // in GLUT to do shutdown and we don't want the Ptr<> to be released at global time
    Noesis::IView* view = Noesis::GUI::CreateView(xaml).GiveOwnership();
    view->SetFlags(Noesis::RenderFlags_PPAA | Noesis::RenderFlags_LCD);
    view->SetSize(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));

    // Renderer initialization with an OpenGL device
    view->GetRenderer()->Init(_device);

    _views.push_back(view);
}

static void CreateOtherView()
{
    // For simplicity purposes we are not using resource providers in this sample. ParseXaml() is
    // enough if there is no extra XAML dependencies
    Noesis::Ptr<Noesis::Grid> xaml(Noesis::GUI::ParseXaml<Noesis::Grid>(R"(<?xml version="1.0" encoding="UTF-8"?>
<Grid 
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
	xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
	xmlns:noesis="clr-namespace:NoesisGUIExtensions;assembly=Noesis.GUI.Extensions">

    <Grid Grid.Column="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        
        <TextBox Grid.Row="3" x:Name="OtherText" Text="8888888" />

    </Grid>
</Grid>
    )"));

    // View creation to render and interact with the user interface
    // We transfer the ownership to a global pointer instead of a Ptr<> because there is no way
    // in GLUT to do shutdown and we don't want the Ptr<> to be released at global time
    Noesis::IView* view = Noesis::GUI::CreateView(xaml).GiveOwnership();
    view->SetFlags(Noesis::RenderFlags_PPAA | Noesis::RenderFlags_LCD);
    view->SetSize(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));

    // Renderer initialization with an OpenGL device
    view->GetRenderer()->Init(_device);

    _views.push_back(view);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
static void DisplayFunc(void)
{
    glViewport(0, 0, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0.0f, 0.0f, 0.25f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    static unsigned long long loop{ 0 };
    loop++;

    for (auto& view : _views)
    {
        // Update view (layout, animations, ...)
        view->Update(glutGet(GLUT_ELAPSED_TIME) / 1000.0);

        // Offscreen rendering phase populates textures needed by the on-screen rendering
        view->GetRenderer()->UpdateRenderTree();
        view->GetRenderer()->RenderOffscreen();
    }

    for (auto& view : _views)
    {
        const std::string test_str = "test string";
        for (auto c : test_str)
        {
            view->Char(c);
        }
        view->Char(VK_RETURN);

        if (loop % 20 == 0)
        {
            Noesis::TextBox* tb = view->GetContent()->FindName<Noesis::TextBox>("ChatInput");
            if (tb != nullptr)
            {
                if (tb->GetIsVisible())
                {
                    tb->SetText("");
                    tb->SetVisibility(Noesis::Visibility_Collapsed);
                }
                else
                {
                    tb->SetVisibility(Noesis::Visibility_Visible);
                    tb->SetText("test-username-value");
                    tb->Focus();
                }
            }
        }

        /*if (loop % 30 == 0)
        {
            Noesis::TextBox* tb = view->GetContent()->FindName<Noesis::TextBox>("ChatSecond");
            if (tb != nullptr)
            {
                if (tb->GetIsVisible())
                {
                    tb->SetText("");
                    tb->SetVisibility(Noesis::Visibility_Collapsed);
                }
                else
                {
                    tb->SetVisibility(Noesis::Visibility_Visible);
                    tb->SetText("test-username-value");
                    tb->Focus();
                }
            }
        }

        if (loop % 40 == 0)
        {
            Noesis::TextBox* tb = view->GetContent()->FindName<Noesis::TextBox>("OtherText");
            if (tb != nullptr)
            {
                if (tb->GetIsVisible())
                {
                    tb->SetText("");
                    tb->SetVisibility(Noesis::Visibility_Collapsed);
                }
                else
                {
                    tb->SetVisibility(Noesis::Visibility_Visible);
                    tb->SetText("test-username-value");
                    tb->Focus();
                }
            }
        }*/
    }
    // My custom render, goes here...
    // My custom render, goes here...
    // My custom render, goes here...
    // My custom render, goes here...

    for (auto& view : _views)
    {
        // If you are going to render here with your own engine you need to restore the GPU state
        // because noesis changes it. In this case only framebuffer and viewport need to be restored
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
        glClearStencil(0);

        // Rendering is done in the active framebuffer
        view->GetRenderer()->Render();
    }

    glutSwapBuffers();
    glutPostRedisplay();
}

////////////////////////////////////////////////////////////////////////////////////////////////////
static void ReshapeFunc(int width, int height)
{
    for (auto& view : _views)
    {
        view->SetSize(width, height);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
static void MouseMoveFunc(int x, int y)
{
    for (auto& view : _views)
    {
        view->MouseMove(x, y);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
static void MouseFunc(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON)
    {
        if (state == GLUT_DOWN)
        {
            for (auto& view : _views)
            {
                view->MouseButtonDown(x, y, Noesis::MouseButton_Left);
            }
        }
        else
        {
            for (auto& view : _views)
            {
                view->MouseButtonUp(x, y, Noesis::MouseButton_Left);
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_STENCIL);
    glutInitWindowSize(1000, 600);

#ifdef __EMSCRIPTEN__
    double width, height;
    emscripten_get_element_css_size("#canvas", &width, &height);
    emscripten_set_canvas_element_size("#canvas", (uint32_t)width, (uint32_t)height);
    glutInitWindowSize((uint32_t)width, (uint32_t)height);
#endif

    glutCreateWindow("NoesisGUI - GLUT integration");
    NoesisInit();

    NoesisApp::TSF::Init();
    NoesisApp::TSF::ActivateWindow(GLUT::hWnd);

    Noesis::GUI::SetSoftwareKeyboardCallback(nullptr, [](void* _user, Noesis::UIElement* _focused, bool _open) {
        if (_open)
        {
            NoesisApp::TSF::ShowKeyboard(_focused);
        }
        else
        {
            NoesisApp::TSF::HideKeyboard();
        }
    });

    CreateBackground();
    CreateChatFrame();
    CreateOtherView();

    glutDisplayFunc(&DisplayFunc);
    glutReshapeFunc(&ReshapeFunc);
    glutPassiveMotionFunc(&MouseMoveFunc);
    glutMouseFunc(&MouseFunc);	

    glutMainLoop();

    return 0;
}
