/*
===========================================================================
Copyright (C) 2015-2024 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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 2 of the License,
or (at your option) any later version.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "ui_local.h"

Event W_MouseExited
	(
	"mouse_exited",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Mouse exiting a widget event"
	);
Event W_MouseEntered
	(
	"mouse_entered",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Mouse Entered a widget event"
	);
Event W_LeftMouseDragged
	(
	"left_mousedragged",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Mouse was moved in a widget with the left button down"
	);
Event W_RightMouseDragged
	(
	"right_mousedragged",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Mouse was moved in a widget with the right button down"
	);
Event W_CenterMouseDragged
	(
	"center_mousedragged",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Mouse was moved in a widget with the center button down"
	);
Event W_MouseMoved
	(
	"mouse_moved",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Mouse was moved in a widget with no buttons down"
	);
Event W_LeftMouseDown
	(
	"left_mouse_down",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Left mouse button has been pressed down"
	);
Event W_LeftMouseUp
	(
	"left_mouse_up",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Left mouse button has been released"
	);
Event W_RightMouseDown
	(
	"right_mouse_down",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Right mouse button has been pressed down"
	);
Event W_RightMouseUp
	(
	"right_mouse_up",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Right mouse button has been released"
	);
Event W_CenterMouseDown
	(
	"center_mouse_down",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Center mouse button has been pressed down"
	);
Event W_CenterMouseUp
	(
	"center_mouse_up",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Center mouse button has been released"
	);
Event W_AnyMouseDown
	(
	"any_mouse_down",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Any mouse button has been pressed"
	);
Event W_AnyMouseUp
	(
	"any_mouse_up",
	EV_DEFAULT,
	"ffi",
	"xpos ypos buttons",
	"Any mouse button has been released"
	);

UIWindowManager uWinMan;

CLASS_DECLARATION(UIWidget, UIWindowManager, NULL) {
    {NULL, NULL}
};

UIWindowManager::UIWindowManager()
{
    m_amidead          = false;
    m_lastbuttons      = 0;
    m_oldview          = NULL;
    m_firstResponder   = NULL;
    m_backgroundwidget = NULL;
    m_cursor           = NULL;
    m_showcursor       = true;
    m_font             = NULL;
    m_bindactive       = NULL;
    setBorderStyle(border_none);
}

UIWindowManager::~UIWindowManager()
{
    m_amidead = true;
}

void UIWindowManager::ViewEvent(UIWidget *view, Event& event, UIPoint2D& pos, int buttons)
{
    if (!view) {
        // use the first responder instead
        view = m_firstResponder;
        if (!view) {
            return;
        }
    }

    if (!view->isEnabled()) {
        return;
    }

    Event *ev = new Event(event);
    ev->AddFloat(pos.x);
    ev->AddFloat(pos.y);
    ev->AddInteger(buttons);
    // send the event to the view
    view->ProcessEvent(ev);
}

UIWidget *UIWindowManager::getResponder(UIPoint2D& pos)
{
    UIWidget *subview;
    UIWidget *responder;
    int       i;

    if (!m_firstResponder) {
        m_firstResponder = this;
    }

    responder = m_firstResponder;

    if (responder != this &&
        // Fixed in OPM
        //  Ignore the first responder if it's disabled
        //  this prevents the UI system from being blocked
        //  trying to activate the control
        responder->CanActivate() && responder->isEnabled()
        ) {
        return responder;
    }

    for (i = m_children.NumObjects(); i > 0; i--) {
        subview = m_children.ObjectAt(i);
        if (subview != m_backgroundwidget) {
            responder = subview->FindResponder(pos);
            if (responder) {
                return responder;
            }
        }
    }

    if (m_backgroundwidget) {
        return m_backgroundwidget->FindResponder(pos);
    }

    return this;
}

void UIWindowManager::Init(const UIRect2D& frame, const char *fontname)
{
    setFrame(frame);
    m_font = new UIFont(fontname);
}

void UIWindowManager::setBackgroundWidget(UIWidget *widget)
{
    m_backgroundwidget = widget;
    if (widget) {
        widget->setParent(this);
        widget->setFrame(m_frame);
    }
}

void UIWindowManager::UpdateViews(void)
{
    int i;
    int n;
    int x, y;

    if (m_backgroundwidget) {
        m_backgroundwidget->Display(m_frame, 1.0);

        n = m_children.NumObjects();
        for (i = 1; i <= n; i++) {
            if (m_children.ObjectAt(i) != m_backgroundwidget) {
                m_children.ObjectAt(i)->Display(m_frame, 1.0);
            }
        }
    } else {
        Display(m_frame, 1.0);
    }

    if (m_cursor && m_showcursor && uid.uiHasMouse) {
        vec4_t col;

        VectorSet4(col, 1, 1, 1, 1);
        set2D();
        uii.Rend_SetColor(col);

        uii.Rend_DrawPicStretched(uid.mouseX, uid.mouseY, 0, 0, 0, 0, 1, 1, m_cursor->GetMaterial());

        m_font->setColor(UWhite);

        if (UI_GetCvarInt("ui_drawcoords", 0)) {
            x = uid.mouseX + 10;
            y = uid.mouseY - 10;
            m_font->Print(x, y, va("%d:%d", uid.mouseX, uid.mouseY), -1, m_bVirtual ? m_vVirtualScale : NULL);
        }
    }
}

void UIWindowManager::ServiceEvents(void)
{
    UIPoint2D         pos;
    unsigned int      buttons;
    SafePtr<UIWidget> view;

    pos.x   = uid.mouseX;
    pos.y   = uid.mouseY;
    buttons = uid.mouseFlags;

    if (m_bindactive) {
        view = m_bindactive;
    } else {
        view = getResponder(pos);
    }

    SafePtr<UIWidget> b = m_oldview;

    if (b != view) {
        ViewEvent(m_oldview, W_MouseExited, pos, buttons);
        ViewEvent(view, W_MouseEntered, pos, buttons);
    }

    if (m_oldpos != pos) {
        if ((buttons & 1) && (m_lastbuttons & 1)) {
            ViewEvent(view, W_LeftMouseDragged, pos, buttons);
        }
        if ((buttons & 2) && (m_lastbuttons & 2)) {
            ViewEvent(view, W_RightMouseDragged, pos, buttons);
        }
        if ((buttons & 4) && (m_lastbuttons & 4)) {
            ViewEvent(view, W_CenterMouseDragged, pos, buttons);
        }
        if (!buttons) {
            ViewEvent(view, W_MouseMoved, pos, 0);
        }
    }

    if ((buttons & 1) != (m_lastbuttons & 1)) {
        if (buttons & 1) {
            uii.UI_WantsKeyboard();
            ActivateControl(view);
            ViewEvent(view, W_LeftMouseDown, pos, buttons);
        } else {
            ViewEvent(view, W_LeftMouseUp, pos, buttons);
        }
    }
    if ((buttons & 2) != (m_lastbuttons & 2)) {
        if (buttons & 2) {
            ViewEvent(view, W_RightMouseDown, pos, buttons);
        } else {
            ViewEvent(view, W_RightMouseUp, pos, buttons);
        }
    }
    if ((buttons & 4) != (m_lastbuttons & 4)) {
        if (buttons & 4) {
            ViewEvent(view, W_CenterMouseDown, pos, buttons);
        } else {
            ViewEvent(view, W_CenterMouseUp, pos, buttons);
        }
    }
    if (buttons != m_lastbuttons) {
        ViewEvent(view, W_AnyMouseDown, pos, buttons);
        ViewEvent(view, W_AnyMouseUp, pos, buttons);
    }

    m_lastbuttons = buttons;
    m_oldpos      = pos;
    m_oldview     = view;

    if (!m_cursor) {
        // initialize the cursor
        setCursor("gfx/2d/mouse_cursor");
    }
}

void UIWindowManager::setFirstResponder(UIWidget *responder)
{
    if (!responder) {
        m_firstResponder = this;
    } else {
        m_firstResponder = responder;
    }
}

UIWidget *UIWindowManager::getFirstResponder(void)
{
    if (m_firstResponder != this) {
        return m_firstResponder;
    }
    return NULL;
}

UIWidget *UIWindowManager::ActiveControl(void)
{
    return m_activeControl;
}

void UIWindowManager::ActivateControl(UIWidget *control)
{
    UIWidget *wid;

    for (wid = control; wid && wid != this; wid = wid->getParent()) {
        if (wid->CanActivate() && wid->isEnabled()) {
            break;
        }
    }

    if (!wid || wid == this) {
        return;
    }

    if (wid->CanActivate() && wid->isEnabled()) {
        UIWidget *active = ActiveControl();

        if (wid != active) {
            if (active) {
                m_activeControl = NULL;
                active->SendSignal(W_Deactivated);
                active->SetHovermaterialActive(false);
                active->SetPressedmaterialActive(false);
            }

            if (!active || !ActiveControl()) {
                m_activeControl = wid;
                wid->SetHovermaterialActive(true);
                wid->ActivateOrder();
                wid->BringToFrontPropogated();
                wid->SendSignal(W_Activated);
            }
        }
    }
}

void UIWindowManager::DeactivateCurrentControl(void)
{
    UIWidget *wid = m_activeControl;

    if (wid) {
        m_activeControl = NULL;
        wid->SendSignal(W_Deactivated);
    }
}

UIWidget *UIWindowManager::FindNextControl(UIWidget *control)
{
    UIWidget *widget;
    bool      back_up;

    if (!control) {
        control = this;
    }

    while (control) {
        widget = control->getFirstChild();
        if (widget) {
            if (widget->isEnabled() && widget->CanActivate()) {
                return widget;
            }

            control = widget;
        } else {
            widget = control->getNextSibling();
            if (widget) {
                if (widget->isEnabled() && widget->CanActivate()) {
                    return widget;
                }

                control = widget;
            } else {
                back_up = true;

                while (control->getParent() && back_up) {
                    control = control->getParent();
                    if (control->getNextSibling()) {
                        control = control->getNextSibling();

                        // there is an error in the fakk2 code where the second check if the control is enabled
                        // while the second should check if the control can activate
                        // this especially affects MOHAA
                        if (control->isEnabled() && control->CanActivate()) {
                            return control;
                        }

                        back_up = false;
                    }
                }

                if (control == this) {
                    break;
                }
            }
        }
    }

    return NULL;
}

UIWidget *UIWindowManager::FindPrevControl(UIWidget *control)
{
    UIWidget *tmp_control;
    UIWidget *next_control;

    if (control) {
        tmp_control = this;
        do {
            next_control = tmp_control;
            tmp_control  = FindNextControl(tmp_control);

            if (!tmp_control) {
                return NULL;
            }
        } while (tmp_control != control);
    } else {
        tmp_control = this;
        do {
            next_control = tmp_control;
            tmp_control  = FindNextControl(tmp_control);
        } while (tmp_control);
    }

    if (next_control != this) {
        return next_control;
    }

    return NULL;
}

void UIWindowManager::setCursor(const char *string)
{
    m_cursor = RegisterShader(string);

    if (m_cursor) {
        m_cursorname = string;
    } else {
        uii.Sys_Printf("Could not register shader '%s'\n", string);
    }
}

void UIWindowManager::showCursor(bool show)
{
    m_showcursor = show;
}

void UIWindowManager::CharEvent(int ch)
{
    if (m_activeControl) {
        m_activeControl->CharEvent(ch);
    }
}

qboolean UIWindowManager::KeyEvent(int key, unsigned int time)
{
    UIWidget *selwidget = NULL;

    if (key == K_TAB && uii.Sys_IsKeyDown(K_CTRL)) {
        UIWidget *wid;

        if (m_activeControl && m_activeControl != this) {
            for (selwidget = m_activeControl; selwidget != NULL; selwidget = selwidget->getParent()) {
                if (selwidget->getParent() == this) {
                    break;
                }
            }
        }

        if (!selwidget) {
            selwidget = getFirstChild();
        }

        for (wid = getNextChild(selwidget); wid != NULL; wid = getNextChild(wid)) {
            if (!(selwidget->m_flags & WF_ALWAYS_BOTTOM) && selwidget->CanActivate()) {
                ActivateControl(wid);
                return true;
            }
        }
        for (wid = getFirstChild(); wid != selwidget && wid != NULL; wid = getNextChild(wid)) {
            if (!(selwidget->m_flags & WF_ALWAYS_BOTTOM) && selwidget->CanActivate()) {
                ActivateControl(wid);
                return true;
            }
        }
    } else {
        if (m_bindactive) {
            m_bindactive->KeyEvent(key, time);
        } else if (!(m_activeControl && m_activeControl->KeyEvent(key, time)) && (key == K_DOWNARROW || key == K_UPARROW)) {
            selwidget = m_activeControl;
            if (selwidget && selwidget != this) {
                for (selwidget = m_activeControl; selwidget != NULL; selwidget = selwidget->getParent()) {
                    if (selwidget->getParent() == this) {
                        break;
                    }
                }
            }

            if (selwidget != NULL) {
                if (selwidget->isSubclassOf(UIWidgetContainer)) {
                    UIWidgetContainer *widcon = (UIWidgetContainer *)selwidget;

                    if (key == K_DOWNARROW) {
                        selwidget = widcon->GetNextWidgetInOrder();
                    } else if (key == K_UPARROW) {
                        selwidget = widcon->GetPrevWidgetInOrder();
                    }

                    if (selwidget) {
                        ActivateControl(selwidget);
                    }
                }
            }
        }
    }

    return true;
}

void UIWindowManager::CreateMenus(void)
{
    int i;
    int n;

    menuManager.DeleteAllMenus();

    n = uWinMan.m_children.NumObjects();
    for (i = 1; i <= n; i++) {
        UIWidget *w = uWinMan.m_children.ObjectAt(i);
        if (w->isSubclassOf(UIWidgetContainer)) {
            UIWidgetContainer *wid = (UIWidgetContainer *)w;
            Menu              *m   = new Menu(w->getName());

            m->setFullscreen(wid->isFullscreen());
            m->setVidMode(wid->getVidMode());
            m->AddMenuItem(w);
            m->ForceHide();
        }
    }
}

UIReggedMaterial *UIWindowManager::RegisterShader(const str& name)
{
    int               i;
    UIReggedMaterial *regged;

    for (i = 1; i <= m_materiallist.NumObjects(); i++) {
        regged = m_materiallist.ObjectAt(i);
        if (regged->GetName() == name) {
            return regged;
        }
    }

    regged = new UIReggedMaterial;
    regged->SetMaterial(name);

    m_materiallist.AddObject(regged);
    return regged;
}

UIReggedMaterial *UIWindowManager::RefreshShader(const str& name)
{
    int               i;
    UIReggedMaterial *regged;

    for (i = 1; i <= m_materiallist.NumObjects(); i++) {
        regged = m_materiallist.ObjectAt(i);
        if (regged->GetName() == name) {
            regged->RefreshMaterial();
            return regged;
        }
    }

    regged = new UIReggedMaterial;
    regged->SetMaterial(name);

    m_materiallist.AddObject(regged);
    return regged;
}

void UIWindowManager::CleanupShadersFromList(void)
{
    int i;
    int num;

    num = m_materiallist.NumObjects();
    for (i = 1; i <= num; i++) {
        UIReggedMaterial *regged = m_materiallist.ObjectAt(i);
        regged->CleanupMaterial();
    }
}

void UIWindowManager::RefreshShadersFromList(void)
{
    int i;
    int num;

    num = m_materiallist.NumObjects();
    for (i = 1; i <= num; i++) {
        UIReggedMaterial *regged = m_materiallist.ObjectAt(i);
        regged->ReregisterMaterial();
    }
}

void UIWindowManager::DeactivateCurrentSmart(void)
{
    UIWidget *toset;
    UIWidget *checking;
    UIWidget *sibling;

    toset = NULL;

    if (!m_activeControl) {
        return;
    }

    for (checking = m_activeControl; checking; checking = checking->getParent()) {
        if (!checking->getParent() || (checking->getParent() && checking->getParent()->IsDying())) {
            continue;
        }
        
        for (sibling = checking->getPrevSibling(); sibling && !toset; sibling = sibling->getPrevSibling()) {
            if (sibling->CanActivate()) {
                toset = sibling;
            }
        }

        if (toset) {
            break;
        }

        for (sibling = checking->getLastSibling(); sibling && sibling != checking && !toset;
             sibling = sibling->getPrevSibling()) {
            if (sibling->CanActivate()) {
                toset = sibling;
            }
        }

        if (toset) {
            break;
        }
    }

    if (!toset) {
        uWinMan.DeactivateCurrentControl();
        return;
    }

    if (toset == &uWinMan) {
        Menu     *menu;
        UIWidget *newWid = NULL;

        menu = menuManager.CurrentMenu();

        if (menu) {
            newWid = menu->GetContainerWidget();
        }

        if (newWid) {
            toset = newWid;
        }
    }

    uWinMan.ActivateControl(toset);
}

bool UIWindowManager::IsDead(void)
{
    return m_amidead;
}

int UIWindowManager::AddBinding(str binding)
{
    int      i;
    Binding *bind;

    for (i = 1; i <= bindings.NumObjects(); i++) {
        Binding *b = bindings.ObjectAt(i);

        if (b->binding == binding) {
            return i;
        }
    }

    bind          = new Binding;
    bind->binding = binding;
    uii.Key_GetKeysForCommand(binding, &bind->key1, &bind->key2);
    bindings.AddObject(bind);

    return bindings.IndexOfObject(bind);
}

str UIWindowManager::GetKeyStringForCommand(str command, int index, qboolean alternate, int *key1, int *key2)
{
    str      s;
    str      cmd;
    Binding *bind;

    if (index > bindings.NumObjects()) {
        return "Invalid Command";
    }

    bind = bindings.ObjectAt(index);

    if (command == bind->binding) {
        if (bind->key1 < 0 || bind->key2 < 0) {
            uii.Key_GetKeysForCommand(command, &bind->key1, &bind->key2);
        }

        if (bind->key2 >= 0) {
            cmd = uii.Key_GetCommandForKey(bind->key2);
            if (cmd != bind->binding) {
                bind->key2 = -1;
            }
        }
        if (bind->key1 >= 0) {
            cmd = uii.Key_GetCommandForKey(bind->key1);
            if (cmd != bind->binding) {
                if (bind->key2 < 0) {
                    bind->key1 = -1;
                } else {
                    bind->key1 = bind->key2;
                    bind->key2 = -1;
                }
            }
        }

        if (alternate) {
            if (bind->key2 >= 0) {
                s = uii.Key_KeynumToString(bind->key2);
            } else {
                s = "None";
            }
        } else {
            if (bind->key1 >= 0) {
                s = uii.Key_KeynumToString(bind->key1);
            } else {
                s = "None";
            }
        }

        if (key1) {
            *key1 = bind->key1;
        }
        if (key2) {
            *key2 = bind->key2;
        }
    } else {
        s = "Invalid Command";
    }

    return s;
}

void UIWindowManager::BindKeyToCommand(str command, int key, int index, qboolean alternate)
{
    Binding *bind = bindings.ObjectAt(index);

    if (bind->binding == command || !bind->binding.length()) {
        if (!command.length()) {
            uii.Key_SetBinding(key, "");
            return;
        }

        if (!alternate) {
            if (bind->key1 == key) {
                return;
            }

            if (bind->key2 == key) {
                uii.Key_SetBinding(bind->key1, "");
                uii.Key_SetBinding(bind->key2, "");
                bind->key1 = key;
                bind->key2 = -1;
            } else {
                uii.Key_SetBinding(bind->key1, "");
            }

            if (!command.length()) {
                uii.Key_SetBinding(key, "");
            } else {
                uii.Key_SetBinding(key, command.c_str());
            }
        } else {
            if (bind->key2 == key) {
                return;
            }

            if (bind->key1 == key) {
                uii.Key_SetBinding(bind->key2, "");
            } else {
                uii.Key_SetBinding(bind->key2, "");
                bind->key2 = key;

                if (!command.length()) {
                    uii.Key_SetBinding(key, "");
                } else {
                    uii.Key_SetBinding(key, command.c_str());
                }
            }
        }
    } else {
        warning(
            "UIWindowManager::BindKeyToCommand",
            "Invalid bind command '%s' for widget command '%s'\n",
            command.c_str(),
            bind->binding.c_str()
        );
    }
}

UIWidget *UIWindowManager::BindActive(void)
{
    return m_bindactive;
}

void UIWindowManager::SetBindActive(UIWidget *w)
{
    m_bindactive = w;
}

void UIWindowManager::Shutdown(void)
{
    int i;

    for (i = m_materiallist.NumObjects(); i > 0; i--) {
        UIReggedMaterial *mat = m_materiallist.ObjectAt(i);
        m_materiallist.RemoveObjectAt(i);

        delete mat;
    }

    for (i = bindings.NumObjects(); i > 0; i--) {
        Binding *b = bindings.ObjectAt(i);
        bindings.RemoveObjectAt(i);

        delete b;
    }

    m_cursor = NULL;

    UIWidget::Shutdown();
}

void UIWindowManager::DeactiveFloatingWindows(void)
{
    int       i;
    UIWidget *pWidg;

    for (i = m_children.NumObjects(); i > 0; i--) {
        pWidg = m_children.ObjectAt(i);

        if (pWidg->isSubclassOf(UIFloatingWindow)) {
            pWidg->SendSignal(W_Deactivated);
        }

        if (pWidg->isSubclassOf(UIPopupMenu)) {
            pWidg->ProcessEvent(W_Deactivated);
        }
    }
}

bool UIWindowManager::DialogExists(void)
{
    int       i;
    UIWidget *pWidg;

    for (i = 1; i <= m_children.NumObjects(); i++) {
        pWidg = m_children.ObjectAt(i);
        if (pWidg->isSubclassOf(UIDialog)) {
            return true;
        }
    }

    return false;
}

void UIWindowManager::RemoveAllDialogBoxes(void)
{
    int       i;
    UIWidget *pWidg;

    for (i = m_children.NumObjects(); i > 0; i--) {
        pWidg = m_children.ObjectAt(i);
        if (pWidg->isSubclassOf(UIDialog)) {
            UIDialog *pDlg = (UIDialog *)pWidg;

            pDlg->m_cancel->m_command += "\n";
            uii.Cmd_Stuff(pDlg->m_cancel->m_command);
        }
    }
}
