package electrosphere.renderer.ui.elements;

import java.util.LinkedList;
import java.util.List;

import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL40;
import org.lwjgl.util.yoga.YGLayout;
import org.lwjgl.util.yoga.YGNode;
import org.lwjgl.util.yoga.Yoga;

import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.engine.signal.Signal.SignalType;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.framebuffer.Framebuffer;
import electrosphere.renderer.framebuffer.FramebufferUtils;
import electrosphere.renderer.model.Material;
import electrosphere.renderer.model.Model;
import electrosphere.renderer.texture.Texture;
import electrosphere.renderer.ui.elementtypes.ContainerElement;
import electrosphere.renderer.ui.elementtypes.DrawableElement;
import electrosphere.renderer.ui.elementtypes.Element;
import electrosphere.renderer.ui.elementtypes.NavigableElement;
import electrosphere.renderer.ui.events.Event;
import electrosphere.renderer.ui.events.NavigationEvent;
import electrosphere.renderer.ui.frame.UIFrameUtils;

/**
 * A window
 */
public class Window implements DrawableElement, ContainerElement, NavigableElement {

    /**
     * The color of the window
     */
    Vector4f color = new Vector4f(0.5f);

    /**
     * The child elements of this window
     */
    List<Element> childList = new LinkedList<Element>();

    /**
     * The buffer to draw the contents of the window to
     */
    Framebuffer widgetBuffer;

    /**
     * The material for the window
     */
    Material customMat = new Material();

    /**
     * The position of buffer texture within the render call for the window itself
     */
    Vector3f texPosition = new Vector3f(0,0,0);

    /**
     * The scale of buffer texture within the render call for the window itself
     */
    Vector3f texScale = new Vector3f(1,1,0);

    /**
     * The navigation callback for the window
     */
    NavigationEventCallback navCallback;

    /**
     * Default width of popups
     */
    static final int DEFAULT_POPUP_WIDTH = 1000;

    /**
     * Default height of popups
     */
    static final int DEFAULT_POPUP_HEIGHT = 1000;

    /**
     * Controls whether the decorations for the window draw or not
     */
    boolean showDecorations = true;

    /**
     * Yoga node for controlling placement of the window on the screen
     * IE, if you want to place a window in the upper right hand side of the screen,
     * this node can be used to control placement alignment to accomplish that
     * NOTE: It should always be set to the current size of the window (width, height)
     * NOTE: It is updated every time the applyYoga function is called
     */
    long parentWindowYogaNode = -1;

    /**
     * The frame decoration texture path
     */
    String frameDecoration = AssetDataStrings.UI_FRAME_TEXTURE_DEFAULT_1;

    /**
     * Constructor
     * @param showDecorations
     * @param positionX
     * @param positionY
     * @param width
     * @param height
     */
    @Deprecated
    public Window(OpenGLState openGLState, int positionX, int positionY, int width, int height, boolean showDecorations){
        try {
            widgetBuffer = FramebufferUtils.generateTextureFramebuffer(openGLState, width, height);
        } catch(Exception e){
            LoggerInterface.loggerRenderer.ERROR(e);
        }
        customMat.setTexturePointer(widgetBuffer.getTexture().getTexturePointer());
        this.showDecorations = showDecorations;
        //yoga node for the actually visible part
        this.yogaNode = Yoga.YGNodeNew();
        this.layout = YGNode.create(this.yogaNode).layout();
        //yoga node for placement
        this.parentWindowYogaNode = Yoga.YGNodeNew();
        Yoga.YGNodeInsertChild(this.parentWindowYogaNode, this.yogaNode, 0);
        setParentAlignContent(YogaAlignment.Start);
        setParentAlignItem(YogaAlignment.Start);
        setParentJustifyContent(YogaJustification.Start);
        this.setFlexDirection(YogaFlexDirection.Column);
        this.setWidth(width);
        this.setHeight(height);
    }

    /**
     * Private constructor
     * @param openGLState
     */
    private Window(OpenGLState openGLState){
        try {
            widgetBuffer = FramebufferUtils.generateTextureFramebuffer(openGLState, DEFAULT_POPUP_WIDTH, DEFAULT_POPUP_HEIGHT);
        } catch(Exception e){
            LoggerInterface.loggerRenderer.ERROR(e);
        }
        customMat.setTexturePointer(widgetBuffer.getTexture().getTexturePointer());
        //yoga node for the actually visible part
        this.yogaNode = Yoga.YGNodeNew();
        this.layout = YGNode.create(this.yogaNode).layout();
        //yoga node for placement
        this.parentWindowYogaNode = Yoga.YGNodeNew();
        Yoga.YGNodeInsertChild(this.parentWindowYogaNode, this.yogaNode, 0);
        setParentAlignContent(YogaAlignment.Start);
        setParentAlignItem(YogaAlignment.Start);
        setParentJustifyContent(YogaJustification.Start);
        this.setFlexDirection(YogaFlexDirection.Column);
        this.setMinWidth(DEFAULT_POPUP_WIDTH);
        this.setMinHeight(DEFAULT_POPUP_HEIGHT);
    }

    /**
     * Creates a window
     * @param openGLState The opengl state
     * @param posX The x position of the window
     * @param posY The y position of the window
     * @param width The width of the window
     * @param height The height of the window
     * @param showDecorations true to show window decorations, false otherwise
     * @return The window element
     */
    public static Window create(OpenGLState openGLState, int posX, int posY, int width, int height, boolean showDecorations){
        Window rVal = new Window(openGLState, posX, posY, width, height, showDecorations);
        return rVal;
    }

    /**
     * Creates an expandable window
     * @param openGLState The opengl state
     * @param posX The x position of the window
     * @param posY The y position of the window
     * @param width The width of the window
     * @param height The height of the window
     * @return The window element
     */
    public static Window createExpandable(OpenGLState openGLState, int posX, int posY, int width, int height){
        Window rVal = new Window(openGLState);
        return rVal;
    }

    /**
     * Creates an expandable window
     * @param openGLState The opengl state
     * @param posX The x position of the window
     * @param posY The y position of the window
     * @param width The width of the window
     * @param height The height of the window
     * @return The window element
     */
    public static Window createExpandableCenterAligned(OpenGLState openGLState, int width, int height){
        Window rVal = new Window(openGLState);
        rVal.setParentAlignItem(YogaAlignment.Center);
        rVal.setParentJustifyContent(YogaJustification.Center);
        return rVal;
    }

    @Override
    public void draw(
        RenderPipelineState renderPipelineState,
        OpenGLState openGLState,
        Framebuffer framebuffer,
        int framebufferPosX,
        int framebufferPosY
    ) {
        float ndcWidth =  (float)this.getWidth()/framebuffer.getWidth();
        float ndcHeight = (float)this.getHeight()/framebuffer.getHeight();
        float ndcX =      (float)this.absoluteToFramebuffer(getAbsoluteX(),framebufferPosX)/framebuffer.getWidth();
        float ndcY =      (float)this.absoluteToFramebuffer(getAbsoluteY(),framebufferPosY)/framebuffer.getHeight();
        Vector3f boxPosition = new Vector3f(ndcX,ndcY,0);
        Vector3f boxDimensions = new Vector3f(ndcWidth,ndcHeight,0);
        
        widgetBuffer.bind(openGLState);
        openGLState.glViewport(width, height);
        
        GL40.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        GL40.glClear(GL40.GL_COLOR_BUFFER_BIT | GL40.GL_DEPTH_BUFFER_BIT);

        //grab assets required to render window
        Model planeModel = Globals.assetManager.fetchModel(Globals.imagePlaneModelID);
        Texture windowFrame = Globals.assetManager.fetchTexture(AssetDataStrings.UI_FRAME_TEXTURE_DEFAULT_1);
        
        for(Element child : childList){
            if(child instanceof DrawableElement){
                DrawableElement drawableChild = (DrawableElement) child;
                drawableChild.draw(renderPipelineState,openGLState,widgetBuffer,this.getAbsoluteX(),this.getAbsoluteY());
            }
        }
        //this call binds the screen as the "texture" we're rendering to
        //have to call before actually rendering
        framebuffer.bind(openGLState);
        openGLState.glViewport(framebuffer.getWidth(), framebuffer.getHeight());

        //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 && showDecorations){
        UIFrameUtils.drawFrame(
            AssetDataStrings.UI_FRAME_TEXTURE_DEFAULT_3, color, 48, 12,
            this.getAbsoluteX(), this.getAbsoluteY(), this.getWidth(), this.getHeight(), 
            framebuffer, framebufferPosX, framebufferPosY
        );
            // planeModel.prawUI(ushUniformToMesh("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.d);
        }
        
        //render content of window
        if(planeModel != 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", new Vector4f(1.0f));
            customMat.setTexturePointer(widgetBuffer.getTexture().getTexturePointer());
            planeModel.getMeshes().get(0).setMaterial(customMat);
            planeModel.drawUI();
        }
    }

    /**
     * Destroys the element
     */
    public void destroy(){
        this.yogaNode = Element.NULL_YOGA_ELEMENT;
        for(Element el : this.getChildren()){
            Globals.signalSystem.post(SignalType.YOGA_DESTROY, el);
        }
        this.clearChildren();
        if(this.yogaNode != Element.NULL_YOGA_ELEMENT){
            Yoga.YGNodeFree(this.yogaNode);
        }
    }

    /**
     * clears all children
     */
    public void clear(){
        for(Element el : this.getChildren()){
            Globals.signalSystem.post(SignalType.YOGA_DESTROY, el);
        }
        this.clearChildren();
    }

    @Override
    public void setWidth(int width){
        this.width = width;
        Yoga.YGNodeStyleSetWidth(this.yogaNode, width);
    }

    @Override
    public void setWidthPercent(float width) {
        Yoga.YGNodeStyleSetWidthPercent(yogaNode, width);
    }
    
    @Override
    public void setHeight(int height){
        this.height = height;
        Yoga.YGNodeStyleSetHeight(this.yogaNode, height);
    }

    @Override
    public void setHeightPercent(float height) {
        Yoga.YGNodeStyleSetHeightPercent(yogaNode, height);
    }

    @Override
    public void setMaxWidth(int width) {
        Yoga.YGNodeStyleSetMaxWidth(yogaNode, width);
    }

    @Override
    public void setMaxWidthPercent(float percent) {
        Yoga.YGNodeStyleSetMaxWidthPercent(yogaNode, percent);
    }

    @Override
    public void setMaxHeight(int height) {
        Yoga.YGNodeStyleSetMaxHeight(yogaNode, height);
    }

    @Override
    public void setMaxHeightPercent(float percent) {
        Yoga.YGNodeStyleSetMaxHeight(yogaNode, percent);
    }

    @Override
    public void setMinWidth(int width) {
        Yoga.YGNodeStyleSetMinWidth(yogaNode, width);
    }

    @Override
    public void setMinWidthPercent(float percent) {
        Yoga.YGNodeStyleSetMinWidthPercent(yogaNode, percent);
    }

    @Override
    public void setMinHeight(int height) {
        Yoga.YGNodeStyleSetMinHeight(yogaNode, height);
    }

    @Override
    public void setMinHeightPercent(float percent) {
        Yoga.YGNodeStyleSetMinHeightPercent(yogaNode, percent);
    }

    public int width = 1;
    public int height = 1;
    
    public int absoluteX = 0;
    public int absoluteY = 0;
    
    int marginTop = 0;
    int marginRight = 0;
    int marginBottom = 0;
    int marginLeft = 0;
    
    public boolean visible = false;

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public boolean getVisible() {
        return visible;
    }

    public void setVisible(boolean draw) {
        this.visible = draw;
    }

    public int getMarginTop(){
        return marginTop;
    }

    public int getMarginRight(){
        return marginRight;
    }

    public int getMarginBottom(){
        return marginBottom;
    }

    public int getMarginLeft(){
        return marginLeft;
    }

    public void setMarginTop(int marginTop){
        this.marginTop = marginTop;
    }

    public void setMarginRight(int marginRight){
        this.marginRight = marginRight;
    }

    public void setMarginBottom(int marginBottom){
        this.marginBottom = marginBottom;
    }

    public void setMarginLeft(int marginLeft){
        this.marginLeft = marginLeft;
    }

    //the yoga node id
    long yogaNode = -1;
    //the layout object
    YGLayout layout;

    @Override
    public long getYogaNode() {
        return yogaNode;
    }

    @Override
    public void applyYoga(int parentX, int parentY) {
        if(this.yogaNode != Element.NULL_YOGA_ELEMENT && parentWindowYogaNode != Element.NULL_YOGA_ELEMENT){
            if(
                Globals.WINDOW_WIDTH <= 0 ||
                Globals.WINDOW_HEIGHT <= 0 ||
                width <= 0 ||
                height <= 0
            ){
                String message = "Window has invalid dimensions!\n" +
                "Globals.WINDOW_WIDTH: " + Globals.WINDOW_WIDTH + "\n" +
                "Globals.WINDOW_HEIGHT: " + Globals.WINDOW_HEIGHT + "\n" +
                "width: " + width + "\n" +
                "height: " + height + "\n"
                ;
                throw new IllegalStateException(message);
            }
            Yoga.YGNodeStyleSetWidth(parentWindowYogaNode, Globals.WINDOW_WIDTH);
            Yoga.YGNodeStyleSetHeight(parentWindowYogaNode, Globals.WINDOW_HEIGHT);
            //calculate yoga layout
            Yoga.YGNodeCalculateLayout(parentWindowYogaNode, width, height, Yoga.YGDirectionInherit);
            //get the values from yoga
            float leftRaw = Yoga.YGNodeLayoutGetLeft(yogaNode);
            float topRaw = Yoga.YGNodeLayoutGetTop(yogaNode);
            float widthRaw = Yoga.YGNodeLayoutGetWidth(yogaNode);
            float heightRaw = Yoga.YGNodeLayoutGetHeight(yogaNode);
            //apply the values to this component
            this.absoluteX = (int)leftRaw;
            this.absoluteY = (int)topRaw;
            this.width = (int)widthRaw;
            this.height = (int)heightRaw;
            //apply yoga values to all children
            LoggerInterface.loggerUI.DEBUG("==Apply yoga to windoow==");
            for(Element child : this.getChildren()){
                child.applyYoga(this.absoluteX,this.absoluteY);
            }
        }
    }

    @Override
    public void setDirection(int layout) {
        Yoga.YGNodeStyleSetDirection(yogaNode, Yoga.YGFlexDirectionColumn);
    }

    @Override
    public void setFlexDirection(YogaFlexDirection layout){
        int directionInteger = Yoga.YGFlexDirectionColumn;
        switch(layout){
            case Column:
                directionInteger = Yoga.YGFlexDirectionColumn;
                break;
            case Column_Reverse:
                directionInteger = Yoga.YGFlexDirectionColumnReverse;
                break;
            case Row:
                directionInteger = Yoga.YGFlexDirectionRow;
                break;
            case Row_Reverse:
                directionInteger = Yoga.YGFlexDirectionRowReverse;
                break;
        }
        Yoga.YGNodeStyleSetFlexDirection(yogaNode, directionInteger);
    }

    @Override
    public void setJustifyContent(YogaJustification justification){
        int justificationInteger = Yoga.YGJustifyFlexStart;
        switch(justification){
            case Center:
                justificationInteger = Yoga.YGJustifyCenter;
                break;
            case Start:
                justificationInteger = Yoga.YGJustifyFlexStart;
                break;
            case End:
                justificationInteger = Yoga.YGJustifyFlexEnd;
                break;
            case Around:
                justificationInteger = Yoga.YGJustifySpaceAround;
                break;
            case Between:
                justificationInteger = Yoga.YGJustifySpaceBetween;
                break;
            case Evenly:
                justificationInteger = Yoga.YGJustifySpaceEvenly;
                break;
        }
        Yoga.YGNodeStyleSetJustifyContent(this.yogaNode, justificationInteger);
    }

    @Override
    public void setAlignItems(YogaAlignment alignment){
        int alignmentInteger = Yoga.YGAlignAuto;
        switch(alignment){
            case Auto:
                alignmentInteger = Yoga.YGAlignAuto;
                break;
            case Start:
                alignmentInteger = Yoga.YGAlignFlexStart;
                break;
            case End:
                alignmentInteger = Yoga.YGAlignFlexEnd;
                break;
            case Around:
                alignmentInteger = Yoga.YGAlignSpaceAround;
                break;
            case Between:
                alignmentInteger = Yoga.YGAlignSpaceBetween;
                break;
            case Stretch:
                alignmentInteger = Yoga.YGAlignStretch;
                break;
            case Baseline:
                alignmentInteger = Yoga.YGAlignBaseline;
                break;
            case Center:
                alignmentInteger = Yoga.YGAlignCenter;
                break;
        }
        Yoga.YGNodeStyleSetAlignItems(this.yogaNode, alignmentInteger);
    }

    @Override
    public void setAlignContent(YogaAlignment alignment){
        int alignmentInteger = Yoga.YGAlignAuto;
        switch(alignment){
            case Auto:
                alignmentInteger = Yoga.YGAlignAuto;
                break;
            case Start:
                alignmentInteger = Yoga.YGAlignFlexStart;
                break;
            case End:
                alignmentInteger = Yoga.YGAlignFlexEnd;
                break;
            case Around:
                alignmentInteger = Yoga.YGAlignSpaceAround;
                break;
            case Between:
                alignmentInteger = Yoga.YGAlignSpaceBetween;
                break;
            case Stretch:
                alignmentInteger = Yoga.YGAlignStretch;
                break;
            case Baseline:
                alignmentInteger = Yoga.YGAlignBaseline;
                break;
            case Center:
                alignmentInteger = Yoga.YGAlignCenter;
                break;
        }
        Yoga.YGNodeStyleSetAlignContent(this.yogaNode, alignmentInteger);
    }

    @Override
    public void setAlignSelf(YogaAlignment alignment){
        int alignmentInteger = Yoga.YGAlignAuto;
        switch(alignment){
            case Auto:
                alignmentInteger = Yoga.YGAlignAuto;
                break;
            case Start:
                alignmentInteger = Yoga.YGAlignFlexStart;
                break;
            case End:
                alignmentInteger = Yoga.YGAlignFlexEnd;
                break;
            case Around:
                alignmentInteger = Yoga.YGAlignSpaceAround;
                break;
            case Between:
                alignmentInteger = Yoga.YGAlignSpaceBetween;
                break;
            case Stretch:
                alignmentInteger = Yoga.YGAlignStretch;
                break;
            case Baseline:
                alignmentInteger = Yoga.YGAlignBaseline;
                break;
            case Center:
                alignmentInteger = Yoga.YGAlignCenter;
                break;
        }
        Yoga.YGNodeStyleSetAlignSelf(this.yogaNode, alignmentInteger);
    }

    @Override
    public void addChild(Element child) {
        childList.add(child);
        child.setParent(this);
        if(child instanceof DrawableElement){
            DrawableElement drawableChild = (DrawableElement) child;
            drawableChild.setVisible(false);
            Yoga.YGNodeInsertChild(yogaNode, drawableChild.getYogaNode(), childList.size() - 1);
        }
    }

    @Override
    public List<Element> getChildren() {
        return childList;
    }

    @Override
    public void removeChild(Element child) {
        childList.remove(child);
        child.setParent(null);
        Yoga.YGNodeRemoveChild(yogaNode, child.getYogaNode());
    }

    @Override
    public void clearChildren(){
        Yoga.YGNodeRemoveAllChildren(yogaNode);
        childList.clear();
    }

    public boolean handleEvent(Event event){
        boolean propagate = true;
        if(event instanceof NavigationEvent && navCallback != null){
            if(!navCallback.execute((NavigationEvent)event)){
                propagate = false;
            }
        }
        return propagate;
    }

    @Override
    public void setOnNavigationCallback(NavigationEventCallback callback) {
        navCallback = callback;
    }

    public Element getParent(){
        return null;
    }

    @Override
    public int getChildOffsetX(){
        return 0;
    }

    @Override
    public int getChildOffsetY(){
        return 0;
    }

    @Override
    public float getChildScaleX(){
        return 1;
    }

    @Override
    public float getChildScaleY(){
        return 1;
    }

    /**
     * Sets the alignment of items on the parent to the window yoga node
     * @param alignment The alignment value
     */
    public void setParentAlignItem(YogaAlignment alignment){
        int alignmentInteger = Yoga.YGAlignAuto;
        switch(alignment){
            case Auto:
                alignmentInteger = Yoga.YGAlignAuto;
                break;
            case Start:
                alignmentInteger = Yoga.YGAlignFlexStart;
                break;
            case End:
                alignmentInteger = Yoga.YGAlignFlexEnd;
                break;
            case Around:
                alignmentInteger = Yoga.YGAlignSpaceAround;
                break;
            case Between:
                alignmentInteger = Yoga.YGAlignSpaceBetween;
                break;
            case Stretch:
                alignmentInteger = Yoga.YGAlignStretch;
                break;
            case Baseline:
                alignmentInteger = Yoga.YGAlignBaseline;
                break;
            case Center:
                alignmentInteger = Yoga.YGAlignCenter;
                break;
        }
        Yoga.YGNodeStyleSetAlignItems(this.parentWindowYogaNode, alignmentInteger);
    }

    /**
     * Sets the alignment of content on the parent to the window yoga node
     * @param alignment The alignment
     */
    public void setParentAlignContent(YogaAlignment alignment){
        int alignmentInteger = Yoga.YGAlignAuto;
        switch(alignment){
            case Auto:
                alignmentInteger = Yoga.YGAlignAuto;
                break;
            case Start:
                alignmentInteger = Yoga.YGAlignFlexStart;
                break;
            case End:
                alignmentInteger = Yoga.YGAlignFlexEnd;
                break;
            case Around:
                alignmentInteger = Yoga.YGAlignSpaceAround;
                break;
            case Between:
                alignmentInteger = Yoga.YGAlignSpaceBetween;
                break;
            case Stretch:
                alignmentInteger = Yoga.YGAlignStretch;
                break;
            case Baseline:
                alignmentInteger = Yoga.YGAlignBaseline;
                break;
            case Center:
                alignmentInteger = Yoga.YGAlignCenter;
                break;
        }
        Yoga.YGNodeStyleSetAlignContent(this.parentWindowYogaNode, alignmentInteger);
    }

    /**
     * Sets the justification of the parent yoga node containing this window
     * @param justification The justification mode
     */
    public void setParentJustifyContent(YogaJustification justification){
        int justificationInteger = Yoga.YGJustifyFlexStart;
        switch(justification){
            case Center:
                justificationInteger = Yoga.YGJustifyCenter;
                break;
            case Start:
                justificationInteger = Yoga.YGJustifyFlexStart;
                break;
            case End:
                justificationInteger = Yoga.YGJustifyFlexEnd;
                break;
            case Around:
                justificationInteger = Yoga.YGJustifySpaceAround;
                break;
            case Between:
                justificationInteger = Yoga.YGJustifySpaceBetween;
                break;
            case Evenly:
                justificationInteger = Yoga.YGJustifySpaceEvenly;
                break;
        }
        Yoga.YGNodeStyleSetJustifyContent(this.parentWindowYogaNode, justificationInteger);
    }

    @Override
    public void setWrap(YogaWrap wrap) {
        switch(wrap){
            case WRAP:
                Yoga.YGNodeStyleSetFlexWrap(yogaNode, Yoga.YGWrapWrap);
                break;
            case NO_WRAP:
                Yoga.YGNodeStyleSetFlexWrap(yogaNode, Yoga.YGWrapNoWrap);
                break;
            case REVERSE:
                Yoga.YGNodeStyleSetFlexWrap(yogaNode, Yoga.YGWrapReverse);
                break;
        }
    }

    @Override
    public void setOverflow(YogaOverflow overflow){
        switch(overflow){
            case Visible:
                Yoga.YGNodeStyleSetOverflow(yogaNode, Yoga.YGOverflowVisible);
                break;
            case Hidden:
                Yoga.YGNodeStyleSetOverflow(yogaNode, Yoga.YGOverflowHidden);
                break;
            case Scroll:
                Yoga.YGNodeStyleSetOverflow(yogaNode, Yoga.YGOverflowScroll);
                break;
        }
    }

    @Override
    public void setAbsolutePosition(boolean useAbsolutePosition) {
        //not implemented
        throw new UnsupportedOperationException();
    }

    @Override
    public void setParent(Element parent) {
        //not implemented
        throw new UnsupportedOperationException();
    }

    /**
     * Converts an absolute (to the screen) position to a position within a framebuffer
     * @param absolutePos The absolute position
     * @param framebufferPos The position of the framebuffer on the screen
     * @return The position within the framebuffer
     */
    public int absoluteToFramebuffer(int absolutePos, int framebufferPos){
        return absolutePos - framebufferPos;
    }

    @Override
    public int getRelativeX() {
        return absoluteX;
    }

    @Override
    public int getRelativeY() {
        return absoluteY;
    }

    @Override
    public int getAbsoluteX() {
        return absoluteX;
    }

    @Override
    public int getAbsoluteY() {
        return absoluteY;
    }

    @Override
    public void setPositionX(int positionX) {
        Yoga.YGNodeStyleSetPosition(this.yogaNode, Yoga.YGEdgeLeft, positionX);
    }

    @Override
    public void setPositionY(int positionY) {
        Yoga.YGNodeStyleSetPosition(this.yogaNode, Yoga.YGEdgeTop, positionY);
    }

    @Override
    public void setPaddingTop(int paddingTop) {
        Yoga.YGNodeStyleSetPadding(this.yogaNode, Yoga.YGEdgeTop, paddingTop);
    }

    @Override
    public void setPaddingRight(int paddingRight) {
        Yoga.YGNodeStyleSetPadding(this.yogaNode, Yoga.YGEdgeRight, paddingRight);
    }

    @Override
    public void setPaddingBottom(int paddingBottom) {
        Yoga.YGNodeStyleSetPadding(this.yogaNode, Yoga.YGEdgeBottom, paddingBottom);
    }

    @Override
    public void setPaddingLeft(int paddingLeft) {
        Yoga.YGNodeStyleSetPadding(this.yogaNode, Yoga.YGEdgeLeft, paddingLeft);
    }

    /**
     * Gets the frame decoration texture path
     * @return The frame decoration texture path
     */
    public String getFrameDecoration() {
        return frameDecoration;
    }

    /**
     * Sets the frame decoration texture path
     * @param frameDecoration The frame decoration texture path
     */
    public void setFrameDecoration(String frameDecoration) {
        this.frameDecoration = frameDecoration;
    }

    /**
     * Gets the color of the window decorations
     * @return The color of the decorations
     */
    public Vector4f getColor() {
        return color;
    }

    /**
     * Sets the color of the window decorations
     * @param color The color of the decorations
     */
    public void setColor(Vector4f color) {
        this.color = color;
    }

    

}
