-- rafftnixGUI.lua
-- author: rafftnix
-- date: 26.05.2013

-- Keine Veränderung ohne meine Erlaubnis!
-- No modification without my permission!
-- history: 
--          v1.0 (August 2013 - GIANTS mod contest Agricultural Service Company hand in)
--            v1.1 (December 2013) - Forrest mod
--             v1.2 (November 2014) - scale and shiftableButton implemented, bugfixes
--            v1.3 (January 2015) - added textFocus, absolute image paths and mouseOver grafics
--            v1.4 (March 2015) - added scroll bar
--             v1.5 (June 2015) - added renderOverlay

RafftnixGUIHelper = {}
RafftnixGUIHelper.modDir = g_currentModDirectory;

function RafftnixGUIHelper:loadMap()
    if not self.gameIsOn then
        self.GUIs = {}
        --g_currentMission.rafftnixGUIHelper = self;
        self.gameIsOn = true;
    end;
end;

function RafftnixGUIHelper:deleteMap()
    if self.gameIsOn then
        for a=1, table.getn(self.GUIs) do
            if self.GUIs[a].delete ~= nil then
                self.GUIs[a]:delete();
            end;
        end;
        self.gameIsOn = false;
    end;
end;

function RafftnixGUIHelper:mouseEvent(posX, posY, isDown, isUp, button)
    for a=1, table.getn(self.GUIs) do
        if self.GUIs[a].mouseEvent ~= nil then
            self.GUIs[a]:mouseEvent(posX, posY, isDown, isUp, button);
        end;
    end;
end;

function RafftnixGUIHelper:keyEvent(unicode, sym, modifier, isDown)
    for a=1, table.getn(self.GUIs) do
        if self.GUIs[a].keyEvent ~= nil then
            self.GUIs[a]:keyEvent(unicode, sym, modifier, isDown);
        end;
    end;
end;

function RafftnixGUIHelper:update(dt)
    for a=1, table.getn(self.GUIs) do
        if self.GUIs[a].update ~= nil then
            self.GUIs[a]:update(dt);
        end;
    end;
end;

function RafftnixGUIHelper:draw()
    for a=1, table.getn(self.GUIs) do
        if self.GUIs[a].draw ~= nil then
            self.GUIs[a]:draw();
        end;
    end;
end;

addModEventListener(RafftnixGUIHelper);


RafftnixGUI = {}
RafftnixGUI.version = 1.5;

local RafftnixGUI_mt = Class(RafftnixGUI);

function RafftnixGUI:new(mt, fullScreenMode, mouseActive, hideGUI)
    local self = {}
    
    if mt == nil then
        mt = RafftnixGUI_mt;
    end;
    setmetatable(self, mt);
    
    table.insert(RafftnixGUIHelper.GUIs, self);
    self.baseElement = self:createElement(nil, 0, 0, 1, 1);
    self.baseElement:setVisibility(false);
    self.fullScreenMode = fullScreenMode;
    self.updateNeededElements = {}
    self.hideGUI = hideGUI;
    self.mouseActive = mouseActive;
    self.mousePosXBackup = 0.5;
    self.mousePosYBackup = 0.5;
    
    return self;
end;

function RafftnixGUI:open()
    if g_gui.currentGui ~= nil and g_gui.currentGui.baseElement ~= nil and g_gui.currentGui.fullScreenMode ~= nil then
        g_gui.currentGui:close();
    end;

    self.baseElement.visibility = true;
    if self.fullScreenMode then
        g_gui:showGui("RafftnixGUI");
        g_currentMission.isPlayerFrozen = true;
        g_gui.currentGui = self;
        
        -- stop vehicles if a gui is open and you can not control the vehicle
        --if g_currentMission.controlledVehicle ~= nil and g_currentMission.controlledVehicle.motor ~= nil then
        --    g_currentMission.controlledVehicle.motor:setSpeedLevel(0, true)
        --end;
    end;
    if self.mouseActive then
        InputBinding.setShowMouseCursor(true);
    end;
end;

function RafftnixGUI:close()
    self.baseElement.visibility = false;
    if self.fullScreenMode then
        InputBinding.setShowMouseCursor(false);
        g_currentMission.isPlayerFrozen = false;
        g_gui.currentGui = nil;
    end;
    if self.mouseActive then
        InputBinding.setShowMouseCursor(false);
    end;
end;

function RafftnixGUI:onOpen()
    self:open();
end;

function RafftnixGUI:onClose()
    self:close();
end;

function RafftnixGUI:delete()
    self:deleteElement(self.baseElement);
end;

function RafftnixGUI:deleteElement(element)
    if element.overlayId ~= nil then
        delete(element.overlayId);
        element.overlayId = nil;
    end;

    for a=1, table.getn(element.elements) do
        self:deleteElement(element.elements[a]);
    end;
    element = nil;
end;

function RafftnixGUI:createElement(baseElement, posX, posY, width, height)
    local element = {}
    element.elements = {}
    element.visibility = true;
    element.posX = posX;
    element.posY = posY;
    element.width = width;
    element.height = height;
    element.isDisabled = false;
    element.isSelected = false;
    element.isSelectable = true;
    element.mouseOver = false;
    element.scaleX = 1;
    element.scaleY = 1;
    
    function element:setVisibility(visibility)
        element.visibility = visibility;
    end;
    
    function element:setPosition(posX, posY)
        element.posX = posX;
        element.posY = posY;
    end;
    
    function element:setScale(scaleX, scaleY)
        element.scaleX = scaleX;
        element.scaleY = scaleY;
    end;
    
    function element:setDimension(width, height)
        element.width = width;
        element.height = height;
    end;
    
    function element:setIsSelectable(isSelectable)
        element.isSelectable = isSelectable;
    end;
    
    function element:setDisabled(state)
        element.isDisabled = state;
        if state then
            if element.textColor ~= nil then
                element.textColor[4] = 0.5;
            end;
        else
            if element.textColor ~= nil then
                element.textColor[4] = 1;
            end;
        end;
        for a=1, table.getn(element.elements) do
            element.elements[a]:setDisabled(state);
        end;
    end;
    
    function element:registerMouseOver(filename, absolutePath)
        if absolutePath == nil or not absolutePath then
            filename = RafftnixGUIHelper.modDir..filename;
        end;
        element.mouseOverOverlayId = createImageOverlay(filename);
    end;
    
    if baseElement ~= nil then
        table.insert(baseElement.elements, element);
    end;
    
    return element;
end;

function RafftnixGUI:createText(baseElement, posX, posY, width, height, text, textSize, textColor)
    local element = self:createElement(baseElement, posX, posY, width, height);
    
    element.text = text;
    element.textSize = textSize;
    element.textFocusX = "left";
    element.textFocusY = "bottom";
    
    
    if textColor == 1 then
        element.textColor = {1, 1, 1, 1};
    else
        element.textColor = textColor;
    end;
    
    function element:setText(text)
        element.text = text;
    end;
    
    function element:setTextColor(textColor)
        element.textColor = textColor;
    end;
    
    function element:setTextFocus(focusX, focusY) -- left middle right, top center bottom
        element.textFocusX = focusX;
        element.textFocusY = focusY;
    end;

    return element;
end;

function RafftnixGUI:createButton(baseElement, posX, posY, width, height, text, textSize, textColor, onClickFunction, object)
    local element = self:createText(baseElement, posX, posY, width, height, text, textSize, textColor, object);
    
    element.onClickFunction = onClickFunction;
    element.onClickCallbackTarget = object;
        
    return element;
end;

function RafftnixGUI:createTextInput(baseElement, posX, posY, width, height, defaultText, textSize, textColor, maxCharacters)
    local element = self:createText(baseElement, posX, posY, width, height, defaultText, textSize, textColor);
    
    element.maxCharacters = Utils.getNoNil(maxCharacters, 30);
    element.textInput = true;
    
    return element;
end;

function RafftnixGUI:createList(baseElement, posX, posY, width, height, heightPerEntry, maxVisibleEntrys, selectEntryCallbackFunction, selectEntryCallbackTarget, markSelectedItem)
    local element = self:createElement(baseElement, posX, posY, width, height);
    
    element.isList = true;
    element.maxVisibleEntrys = maxVisibleEntrys;
    element.selectedEntry = 1;
    element.highestVisibleEntry = 1;
    element.heightPerEntry = heightPerEntry;
    element.selectEntryCallbackFunction = selectEntryCallbackFunction;
    element.selectEntryCallbackTarget = selectEntryCallbackTarget;
    element.markSelectedItem = Utils.getNoNil(markSelectedItem, false);
    
    function element:scrollUp()
        element.highestVisibleEntry = math.max(1, element.highestVisibleEntry-1);
    end;
    
    function element:scrollDown()
        element.highestVisibleEntry = math.min(table.getn(element.elements) - element.maxVisibleEntrys, element.highestVisibleEntry+1);
    end;
    
    return element;
end;

function RafftnixGUI:createImage(baseElement, posX, posY, width, height, filename, absolutePath)
    local element = self:createElement(baseElement, posX, posY, width, height);
    
    if filename == nil then
        print("RafftnixGUI: Image filename missing");
        printCallstack();
    end;
    
    if absolutePath == nil or not absolutePath then
        filename = RafftnixGUIHelper.modDir..filename;
    end;
    
    element.overlayId = createImageOverlay(filename);
    return element;
end;

function RafftnixGUI:createImgButton(baseElement, posX, posY, width, height, filename, onClickFunction, object, absolutePath)
    local element = self:createButton(baseElement, posX, posY, width, height, "", 1, 1, onClickFunction, object);
    
    if absolutePath == nil or not absolutePath then
        filename = RafftnixGUIHelper.modDir..filename;
    end;
    
    element.overlayId = createImageOverlay(filename);
    return element;
end;

function RafftnixGUI:createShiftableImgButton(baseElement, posX, posY, width, height, filename, onClickFunction, object, absolutePath)
    local element = self:createButton(baseElement, posX, posY, width, height, "", 1, 1, onClickFunction, object);
    element.isShiftable = true;
    element.isShifting = false;
    
    if absolutePath == nil or not absolutePath then
        filename = RafftnixGUIHelper.modDir..filename;
    end;
    
    element.overlayId = createImageOverlay(filename);
    return element;
end;

function RafftnixGUI:createScrollBar(baseElement, posX, posY, width, height, needleWidth, maxXScroll, onScrollFunction, object, filename, absolutePath)
    local element = self:createElement(baseElement, posX, posY, width, height);
    element.onScrollFunction = onScrollFunction;
    element.onScrollCallbackTarget = object;
    element.needleWidth = needleWidth;
    element.maxXScroll = maxXScroll;
    element.value = 0;
    
    function element:computeShiftableImgButton(diffX, diffY)
        local posX = element.scrollBarNeedle.posX+diffX;
        posX = math.min(posX, element.maxXScroll-element.needleWidth);
        posX = math.max(posX, 0);
        element.scrollBarNeedle:setPosition(posX, element.scrollBarNeedle.posY);
        element.value = posX/(element.maxXScroll-element.needleWidth);
        element.onScrollFunction(element.onScrollCallbackTarget, element.value);
    end;
    
    function element:getValue()
        return element.value;
    end;
    
    function element:setValue(value)
        value = math.max(0, value);
        value = math.min(1, value);
        local posX = (element.maxXScroll-element.needleWidth)*value;
        element.scrollBarNeedle:setPosition(posX, element.scrollBarNeedle.posY);
    end;
    
    element.scrollBarNeedle = self:createShiftableImgButton(baseElement, 0, 0, needleWidth, height, filename, element.computeShiftableImgButton, element, absolutePath);

    return element;
end;

function RafftnixGUI:createRenderOverlay(baseElement, posX, posY, width, height, cameraId, resolutionX, resolutionY, aspectRatio)
    -- attention: resolution needs to be 2^n
    local element = self:createElement(baseElement, posX, posY, width, height);
    element.overlayId = createRenderOverlay(cameraId, aspectRatio, resolutionX, resolutionY);
        
    function element:update(dt)
        updateRenderOverlay(element.overlayId);
    end;
        
    table.insert(self.updateNeededElements, element);
    return element;
end;

function RafftnixGUI:mouseEvent(posX, posY, isDown, isUp, button)
    if self.baseElement.visibility then
        self:checkElementOnMouseEvent(self.baseElement, posX, posY, 0, 0, 1, 1, isDown, isUp, button);
    end;    
    self.mousePosXBackup = posX;
    self.mousePosYBackup = posY;
end;

function RafftnixGUI:keyEvent(unicode, sym, modifier, isDown)
    if self.baseElement.visibility then
        self:checkElementOnKeyEvent(self.baseElement, unicode, sym, modifier, isDown);
    end;
end;

function RafftnixGUI:checkElementOnKeyEvent(element, unicode, sym, modifier, isDown)
    if element.textInput ~= nil and element.textInput and element.visibility and isDown then
        if element.maxCharacters ~= nil and element.maxCharacters > string.len(element.text) then
            if (unicode > 96 and unicode < 123) or (unicode > 64 and unicode < 91) or unicode == 32 or unicode == 94 or unicode == 93 or unicode == 91 or unicode == 123 or unicode == 125 or unicode == 92 or unicode == 223 or unicode == 63 or unicode == 33 or unicode == 34 or (unicode > 48 and unicode < 60) or (unicode > 39 and unicode < 48) or unicode == 124 then 
                element:setText(element.text .. string.char(unicode));
            end;
        end;
        
        if unicode == 8 then
            local length = string.len(element.text);
            element:setText(string.sub(element.text, 1, length-1));
        end;
    end;

    if element.visibility then
        for a=1, table.getn(element.elements) do
            self:checkElementOnKeyEvent(element.elements[a], unicode, sym, modifier, isDown);
        end;
    end;
end;

function RafftnixGUI:checkElementOnMouseEvent(element, mouseX, mouseY, xParent, yParent, scaleX, scaleY, isDown, isUp, button)
    local scaleX = scaleX * element.scaleX;
    local scaleY = scaleY * element.scaleY;
    element.mouseOver = false;
    if element.visibility and not element.isDisabled then
        if mouseX > ((element.posX*scaleX)+xParent) and mouseX < (((element.posX*scaleX)+xParent) + (element.width*scaleX)) then 
            if mouseY > ((element.posY*scaleY)+yParent) and mouseY < (((element.posY*scaleY)+yParent) + (element.height*scaleY)) then
                if (element.onClickFunction ~= nil or (element.isList ~= nil and element.isList)) and isDown and button == 1 then
                    if element.isShiftable then
                        element.isShifting = true;
                    elseif element.onClickFunction ~= nil then
                        element.onClickFunction(element.onClickCallbackTarget);
                    end;
                    if element.isList then
                        local y = ((element.posY*scaleX)+yParent) + (element.height*scaleY);
                        
                        local start = element.highestVisibleEntry;
                        local stop = math.min(element.highestVisibleEntry + element.maxVisibleEntrys - 1, table.getn(element.elements));

                        for a=start, stop do
                            local entry = element.elements[a];
                            if mouseY < y and mouseY > (y - (element.heightPerEntry*scaleY)) then
                                element.elements[element.selectedEntry].isSelected = false;
                                element.selectEntryCallbackFunction(element.selectEntryCallbackTarget, a);
                                element.selectedEntry = a;
                                element.elements[element.selectedEntry].isSelected = true;
                                break;
                            end;
                            
                            y = y - (element.heightPerEntry*scaleY);
                        end;
                    end;
                end;
                
                element.mouseOver = true;
                
                if element.isList ~= nil and element.isList and isDown then
                    if button == 4 then
                        element.scrollUp();
                    end;
                    if button == 5 then
                        element.scrollDown();
                    end;
                end;
            end;
        end;
        if not isDown and button == 1 and element.isShifting then
            element.isShifting = false;
        end;
    end;
    
    if element.isShifting then
        local diffX = mouseX - self.mousePosXBackup;
        local diffY = mouseY - self.mousePosYBackup;
        --element.setPosition(element.posX+diffX, element.posY+diffY);
        element.onClickFunction(element.onClickCallbackTarget, diffX, diffY, element.posX, element.posY);
    end;

    if element.visibility then
        for a=1, table.getn(element.elements) do
            self:checkElementOnMouseEvent(element.elements[a], mouseX, mouseY, element.posX*scaleX+xParent, element.posY*scaleY+yParent, scaleX, scaleY, isDown, isUp, button);
        end;
    end;
end;

function RafftnixGUI:update(dt)
    for a=1, table.getn(self.updateNeededElements) do
        local element = self.updateNeededElements[a];
        if element.visibility then
            element:update(dt);
        end;
    end;
end;

function RafftnixGUI:draw()
    setTextBold(false);
    if not self.hideGUI then
        self:renderElement(self.baseElement, 0, 0, 1, 1, false);
    end;
end;

function RafftnixGUI:renderElement(element, x, y, scaleX, scaleY, parentElementSelected)
    scaleX = scaleX * element.scaleX;
    scaleY = scaleY * element.scaleY;

    local textScale = math.min(scaleX, scaleY);

    if element.visibility then
        x = x + element.posX*scaleX;
        y = y + element.posY*scaleY;

        if element.text ~= nil then 
            if element.markSelectedItem and (element.isSelected or parentElementSelected) then
                setTextColor(0,1,0,element.textColor[4]);
            else
                setTextColor(unpack(element.textColor));
            end;
            setTextAlignment(RenderText.ALIGN_LEFT);
            
            -- find correckt text position (focus)
            local textX = 0;
            local textY = 0;
            
            if element.textFocusX == "left" then
                textX = x;
            elseif element.textFocusX == "middle" then
                textX = x + 0.5*(element.width*scaleX) - 0.5*getTextWidth(element.textSize*textScale, element.text);
            elseif element.textFocusX == "right" then
                textX = x + (element.width*scaleX) - getTextWidth(element.textSize*textScale, element.text);
            end;
            
            if element.textFocusY == "top" then
                textY = y + element.height*scaleY - getTextHeight(element.textSize*textScale, element.text);
            elseif element.textFocusY == "center" then
                textY = y + 0.5*(element.height*scaleY) - 0.5*getTextHeight(element.textSize*textScale, element.text);
            elseif element.textFocusY == "bottom" then
                textY = y;
            end;
            
            renderText(textX, textY, element.textSize*textScale, element.text);
            setTextColor(1, 1, 1, 1);
        end;
        if element.overlayId ~= nil then 
            renderOverlay(element.overlayId, x, y, element.width*scaleX, element.height*scaleY);
        end;
        
        if element.isList ~= nil then -- list
            local start = element.highestVisibleEntry;
            local stop = math.min(element.highestVisibleEntry + element.maxVisibleEntrys - 1, table.getn(element.elements));
            y = element.posY + element.height;
            
            for a=start, stop do
                if element.elements[a] ~= nil then
                    y = y - element.heightPerEntry;
                    
                    local parentElementSelected = false;
                    if element.selectedEntry == a then
                        parentElementSelected = true;
                    end;
                    self:renderElement(element.elements[a], x, y, scaleX, scaleY, parentElementSelected);
                end;
            end;
        else
            for a=1, table.getn(element.elements) do
                self:renderElement(element.elements[a], x, y, scaleX, scaleY, parentElementSelected);
            end;
        end;
        if element.mouseOverOverlayId ~= nil and element.mouseOver then
            renderOverlay(element.mouseOverOverlayId, x, y, element.width*scaleX, element.height*scaleY);
        end;
    end;
end;