package electrosphere.renderer.ui.elements;

import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
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.KeyEventElement;
import electrosphere.renderer.ui.elementtypes.ValueElement;
import electrosphere.renderer.ui.events.ClickEvent;
import electrosphere.renderer.ui.events.Event;
import electrosphere.renderer.ui.events.FocusEvent;
import electrosphere.renderer.ui.events.KeyboardEvent;
import electrosphere.renderer.ui.events.ValueChangeEvent;
import electrosphere.renderer.ui.font.Font;

import org.joml.Vector3f;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.yoga.Yoga;

import java.util.regex.Pattern;

/**
 * A Text input
 */
public class TextInput extends StandardContainerElement implements DrawableElement, FocusableElement, KeyEventElement, ClickableElement, ValueElement {

    Vector3f backgroundColor = new Vector3f(0.2f,0.2f,0.2f);

    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();
    
    public boolean visible = false;

    boolean focused = false;
    FocusEventCallback onFocusCallback;
    FocusEventCallback onLoseFocusCallback;
    KeyboardEventCallback onKeyPressCallback;
    ClickEventCallback onClickCallback;
    ValueChangeEventCallback onValueChangeCallback;
    Vector3f color;

    String text = "";
    int textPixelWidth = 0;
    
    float fontSize = 1.0f;
    

    Font font;

    /**
     * Creates a text input element using the default font size
     * @return The text input
     */
    public static TextInput createTextInput(){
        return new TextInput(Label.DEFAULT_FONT_SIZE);
    }
    
    /**
     * Private constructor
     * @param fontSize
     */
    private TextInput(float fontSize){
        super();
        this.font = Globals.fontManager.getFont("default");
        this.fontSize = fontSize;
        this.color = new Vector3f(1,1,1);
        setHeight((int)(font.getFontHeight() * fontSize));
        Yoga.YGNodeStyleSetFlexDirection(this.yogaNode, Yoga.YGFlexDirectionRow);
        Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.imageHeight * fontSize);
        Yoga.YGNodeStyleSetMinWidth(this.yogaNode, 1);
    }

    /**
     * Constructor
     * @param x
     * @param y
     * @param fontSize
     */
    public TextInput(int x, int y, float fontSize){
        super();
        this.font = Globals.fontManager.getFont("default");
        this.fontSize = fontSize;
        this.color = new Vector3f(1,1,1);
        setHeight((int)(font.getFontHeight() * fontSize));
        Yoga.YGNodeStyleSetFlexDirection(this.yogaNode, Yoga.YGFlexDirectionRow);
        Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.imageHeight * fontSize);
        Yoga.YGNodeStyleSetMinWidth(this.yogaNode, 1);
    }
    
    void generateLetters(){
        for(Element el : getChildren()){
            Yoga.YGNodeRemoveChild(this.yogaNode,el.getYogaNode());
            el.destroy();
        }
        childList.clear();
        for(int i = 0; i < text.length(); i++){
            char toDraw = text.charAt(i);
            Vector3f bitMapDimension = this.font.getDimensionOfCharacterDiscrete(toDraw);
            BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.height, toDraw);
            newLetter.setColor(color);
            this.addChild(newLetter);
        }
    }
    
    public void setText(String text){
        this.text = text;
        textPixelWidth = 0;
        for(int i = 0; i < text.length(); i++){
            Vector3f bitMapDimension = this.font.getDimensionOfCharacterDiscrete(text.charAt(i));
            textPixelWidth = textPixelWidth + (int)bitMapDimension.x;
        }
        generateLetters();
    }

    public void setColor(Vector3f color){
        this.color.set(color);
        for(Element character : childList){
            ((BitmapCharacter)character).setColor(color);
        }
    }
    
    public String getText(){
        return text;
    }

    @Override
    public void draw(
        RenderPipelineState renderPipelineState,
        OpenGLState openGLState,
        int parentFramebufferPointer,
        int parentPosX,
        int parentPosY,
        int parentWidth,
        int parentHeight
    ) {
        //
        //Draw decorations

        float ndcWidth =  (float)getInternalWidth()/parentWidth;
        float ndcHeight = (float)getInternalHeight()/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", backgroundColor);
            customMat.setTexturePointer(windowFrame.getTexturePointer());
            planeModel.getMeshes().get(0).setMaterial(customMat);
            planeModel.drawUI();
        }


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

    public boolean getVisible() {
        return visible;
    }

    public void setVisible(boolean draw) {
        this.visible = draw;
    }
    
    public boolean handleEvent(Event event){
        boolean propagate = true;
        if(event instanceof FocusEvent){
            FocusEvent focusEvent = (FocusEvent)event;
            if(focusEvent.isFocused()){
                if(this.onFocusCallback != null){
                    this.onFocusCallback.execute(focusEvent);
                } else {
                    this.focused = true;
                    this.setColor(new Vector3f(1,0,0));
                    propagate = false;
                }
            } else {
                if(this.onLoseFocusCallback != null){
                    this.onLoseFocusCallback.execute(focusEvent);
                } else {
                    this.focused = false;
                    this.setColor(new Vector3f(1,1,1));
                    propagate = false;
                }
            }
        } else if(event instanceof KeyboardEvent){
            KeyboardEvent keyEvent = (KeyboardEvent)event;
            if(onKeyPressCallback != null){
                onKeyPressCallback.execute(keyEvent);
            } else {
                propagate = defaultKeyHandling(keyEvent);
            }
        } else if(event instanceof ClickEvent){
            ClickEvent clickEvent = (ClickEvent)event;
            if(onClickCallback != null){
                onClickCallback.execute(clickEvent);
            } else {
                Globals.elementManager.focusElement(this);
                propagate = false;
            }
        } else if(event instanceof ValueChangeEvent){
            ValueChangeEvent valueEvent = (ValueChangeEvent)event;
            if(this.onValueChangeCallback != null){
                this.onValueChangeCallback.execute(valueEvent);
            }
        }
        return propagate;
    }

    /**
     * The default handling for a keyboard event
     * @param keyEvent the event
     * @return whether to propagate or not
     */
    public boolean defaultKeyHandling(KeyboardEvent keyEvent){
        if(keyEvent.getKey().matches(Pattern.quote("bs"))){
            if(this.text.length() > 0){
                this.setText(this.text.substring(0, this.text.length() - 1));
            }
        } else {
            this.setText(this.text + keyEvent.getKey());
        }
        //fire value change event
        Globals.elementManager.fireEventNoPosition(new ValueChangeEvent(text), this);
        return false;
    }

    @Override
    public boolean isFocused() {
        return focused;
    }

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

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

    @Override
    public void setOnPress(KeyboardEventCallback callback) {
        onKeyPressCallback = callback;
    }

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

    @Override
    public void setOnValueChangeCallback(ValueChangeEventCallback callback) {
        this.onValueChangeCallback = callback;
    }

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