package electrosphere.renderer.ui.elements;

import org.joml.Vector3f;
import org.lwjgl.opengl.GL30;

import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.debug.DebugRendering;
import electrosphere.renderer.model.Material;
import electrosphere.renderer.model.Model;
import electrosphere.renderer.texture.Texture;
import electrosphere.renderer.ui.elementtypes.ClickableElement;
import electrosphere.renderer.ui.elementtypes.DrawableElement;
import electrosphere.renderer.ui.elementtypes.Element;
import electrosphere.renderer.ui.elementtypes.FocusableElement;
import electrosphere.renderer.ui.elementtypes.HoverableElement;
import electrosphere.renderer.ui.events.ClickEvent;
import electrosphere.renderer.ui.events.Event;
import electrosphere.renderer.ui.events.FocusEvent;
import electrosphere.renderer.ui.events.HoverEvent;
import electrosphere.renderer.ui.events.MouseEvent;

public class Button extends StandardContainerElement implements DrawableElement, FocusableElement, ClickableElement, HoverableElement {

    static Vector3f color = new Vector3f(1.0f);

    Vector3f boxPosition = new Vector3f();
    Vector3f boxDimensions = new Vector3f();
    Vector3f texPosition = new Vector3f(0,0,0);
    Vector3f texScale = new Vector3f(1,1,0);
    Material customMat = new Material();

    boolean visible = false;
    boolean focused = false;

    FocusEventCallback onFocusCallback;
    FocusEventCallback onLoseFocusCallback;
    ClickEventCallback clickCallback;
    HoverEventCallback hoverEventCallback;

    static final Vector3f windowDrawDebugColor = new Vector3f(1.0f,1.0f,1.0f);

    public Button(){
        super();
    }

    /**
     * Creates a button with 
     * @param text
     * @return
     */
    public static Button createButton(String text, ClickableElement.ClickEventCallback callback){
        Button rVal = new Button();
        Label rValLabel = new Label(1.0f);
        rValLabel.setText(text);
        rVal.addChild(rValLabel);
        rVal.setOnClick(callback);
        return rVal;
    }

    /**
     * Creates a button that fires a callback when clicked
     * @param text The text for the button label
     * @param callback The callback
     * @return The button
     */
    public static Button createButton(String text, Runnable callback){
        Button rVal = new Button();
        Label rValLabel = new Label(1.0f);
        rValLabel.setText(text);
        rVal.addChild(rValLabel);
        rVal.setOnClick(new ClickableElement.ClickEventCallback(){public boolean execute(ClickEvent event){
            callback.run();
            return false;
        }});
        return rVal;
    }

    public boolean getVisible() {
        return visible;
    }

    public void setVisible(boolean draw) {
        this.visible = draw;
    }
    
    @Override
    public boolean isFocused() {
        return focused;
    }

    void onFocus(FocusEvent event) {
        if(onFocusCallback != null){
            onFocusCallback.execute(event);
        } else {
            for(Element child : childList){
                if(child instanceof Label){
                    Label childLabel = (Label) child;
                    childLabel.setColor(new Vector3f(1,0,0));
                }
            }
        }
    }

    void onLoseFocus(FocusEvent event) {
        if(onLoseFocusCallback != null){
            onLoseFocusCallback.execute(event);
        } else {
            for(Element child : childList){
                if(child instanceof Label){
                    Label childLabel = (Label) child;
                    childLabel.setColor(new Vector3f(1,1,1));
                }
            }
        }
    }

    /**
     * Default hover event handling
     * @param event the hover event
     */
    void onHoverEvent(HoverEvent event){
        if(event.isHovered()){
            for(Element child : childList){
                if(child instanceof Label){
                    Label childLabel = (Label) child;
                    childLabel.setColor(new Vector3f(1,0,0));
                }
            }
        } else {
            for(Element child : childList){
                if(child instanceof Label){
                    Label childLabel = (Label) child;
                    childLabel.setColor(new Vector3f(1,1,1));
                }
            }
        }
    }

    public void draw(
        RenderPipelineState renderPipelineState,
        OpenGLState openGLState,
        int parentFramebufferPointer,
        int parentPosX,
        int parentPosY,
        int parentWidth,
        int parentHeight
    ) {

        //
        //Draw decorations

        float ndcWidth =  (float)getWidth()/parentWidth;
        float ndcHeight = (float)getHeight()/parentHeight;
        float ndcX =      (float)(getInternalX() + parentPosX)/parentWidth;
        float ndcY =      (float)(getInternalY() + parentPosY)/parentHeight;
        boxPosition = new Vector3f(ndcX,ndcY,0);
        boxDimensions = new Vector3f(ndcWidth,ndcHeight,0);
        
        Model planeModel = Globals.assetManager.fetchModel(Globals.imagePlaneModelID);
        Texture windowFrame = null;
        if(this.isFocused()){
            windowFrame = Globals.assetManager.fetchTexture("Textures/ui/uiFrame2.png");
        } else {
            windowFrame = Globals.assetManager.fetchTexture("Textures/ui/uiFrame1.png");
        }

        //this call binds the screen as the "texture" we're rendering to
        //have to call before actually rendering
        openGLState.glBindFramebuffer(GL30.GL_FRAMEBUFFER, parentFramebufferPointer);
        openGLState.glViewport(parentWidth, parentHeight);

        //error if assets are null
        if(planeModel == null || windowFrame == null){
            LoggerInterface.loggerRenderer.ERROR("Window unable to find plane model or window frame!!", new Exception());
        }

        //render background of window
        if(planeModel != null && windowFrame != null){
            planeModel.pushUniformToMesh("plane", "mPosition", boxPosition);
            planeModel.pushUniformToMesh("plane", "mDimension", boxDimensions);
            planeModel.pushUniformToMesh("plane", "tPosition", texPosition);
            planeModel.pushUniformToMesh("plane", "tDimension", texScale);
            planeModel.pushUniformToMesh(planeModel.getMeshes().get(0).getMeshName(), "color", color);
            customMat.setTexturePointer(windowFrame.getTexturePointer());
            planeModel.getMeshes().get(0).setMaterial(customMat);
            planeModel.drawUI();
        }


        //
        //Draw children elements
        for(Element child : childList){
            if(child instanceof DrawableElement){
                DrawableElement drawableChild = (DrawableElement) child;
                drawableChild.draw(
                    renderPipelineState,
                    openGLState,
                    parentFramebufferPointer,
                    parentPosX + this.internalPositionX,
                    parentPosY + this.internalPositionY,
                    parentWidth,
                    parentHeight
                );
            }
        }

        if(Globals.RENDER_FLAG_RENDER_UI_BOUNDS && DebugRendering.RENDER_DEBUG_OUTLINE_BUTTON){
            DebugRendering.drawUIBounds(parentFramebufferPointer, boxPosition, boxDimensions, windowDrawDebugColor);
        }
    }

    @Override
    public void setOnFocus(FocusEventCallback callback) {
        onFocusCallback = callback;
    }

    @Override
    public void setOnLoseFocus(FocusEventCallback callback) {
        onLoseFocusCallback = callback;
    }

    @Override
    public void setOnClick(ClickEventCallback callback) {
        clickCallback = callback;
    }

    public boolean handleEvent(Event event){
        if(event instanceof MouseEvent){
            return false;
        }
        if(event instanceof FocusEvent){
            FocusEvent focusEvent = (FocusEvent) event;
            if(focusEvent.isFocused()){
                this.focused = true;
                onFocus(focusEvent);
            } else {
                this.focused = false;
                onLoseFocus(focusEvent);
            }
            return false;
        }
        if(event instanceof ClickEvent){
            if(clickCallback != null){
                clickCallback.execute((ClickEvent)event);
            }
        }
        if(event instanceof HoverEvent){
            if(hoverEventCallback != null){
                hoverEventCallback.execute((HoverEvent)event);
            } else {
                //default hover handling
                onHoverEvent((HoverEvent)event);
            }
        }
        return true;
    }

    @Override
    public void setOnHoverCallback(HoverEventCallback callback) {
        this.hoverEventCallback = callback;
    }

    @Override
    public void setFocused(boolean focused) {
        this.focused = focused;
    }
    
}
