// Copyright (c) 2022 Dominic Masters
// 
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

#pragma once
#include "scene/components/ui/UICanvas.hpp"
#include "display/Color.hpp"
#include "util/array.hpp"
#include "util/mathutils.hpp"
#include "display/shader/Shader.hpp"
#include "state/State.hpp"

namespace Dawn {
  enum UIComponentAlign {
    UI_COMPONENT_ALIGN_START,
    UI_COMPONENT_ALIGN_MIDDLE,
    UI_COMPONENT_ALIGN_END,
    UI_COMPONENT_ALIGN_STRETCH
  };

  class UIGrid;
  
  class UIComponent : public StateOwner {
    protected:
      // Calculated (and cached) values
      float_t width = 1;
      float_t height = 1;
      float_t relativeX = 0;
      float_t relativeY = 0;

      // Setting values
      UIComponentAlign alignX = UI_COMPONENT_ALIGN_START;
      UIComponentAlign alignY = UI_COMPONENT_ALIGN_START;
      glm::vec4 alignment = glm::vec4(0, 0, 32, 32);
      float_t z = 0;
      std::vector<UIComponent*> children;
      UIComponent *parent = nullptr;

      // Events
      Event<UIComponent*> eventAlignmentUpdated;
      
      // I currently don't support rotation or scale. Not because I can't but
      // because it's basically un-necessary. Unity does support rotation but
      // it doesn't affect how the alignment side of things work (similar to how
      // CSS would handle things) When I need to support these I will add the 
      // code but right now it's not necessary

      /**
       * Updates the cached/stored values based on the setting internal values.
       * You should watchdog this if you intend to do something when values are 
       * updated, e.g. if you need to resize a quad, or something.
       */
      virtual void updatePositions();

      /**
       * Intended to be overwritten by subclass. Called by the draw method to
       * ask this child to draw.
       * 
       * @param projection Projection matrix of the camera.
       * @param view View matrix of the camera.
       * @param parent Matrix of the parent of this UI item.
       * @return The list of shader pass items.
       */
      virtual std::vector<struct ShaderPassItem> getSelfPassItems(
        glm::mat4 projection,
        glm::mat4 view,
        glm::mat4 transform
      ) = 0;

    public:
      /**
       * Method used to calculate alignment dimensions.
       * 
       * @param align Alignment  value enumator.
       * @param position Output position floating point.
       * @param size Output size floating point.
       * @param outerSize Outer size (of the parent).
       * @param innerSize Inner size (of this element's content).
       * @param alignment Alignment settings.
       */
      static void calculateDimensions(
        enum UIComponentAlign align,
        float_t *position,
        float_t *size,
        float_t outerSize,
        float_t innerSize,
        glm::vec2 alignment
      );

      UICanvas *canvas;

      UIComponent(UICanvas *canvas);

      /**
       * Returns the calculated width, based on the internal alignment values.
       * 
       * @return Width of the component.
       */
      virtual float_t getWidth();

      /**
       * Returns the calculated height, based on the internal alignment values.
       * 
       * @return Height of the component.
       */
      virtual float_t getHeight();

      /**
       * Returns the internal width of the content within this element, e.g. 
       * the content width.
       * 
       * @return Content width.
       */
      virtual float_t getContentWidth();

      /**
       * Returns the internal height of the content within this element, e.g. 
       * the content height.
       * 
       * @return Content height.
       */
      virtual float_t getContentHeight();

      /**
       * Returns the X position, relative to this components' parent.
       * 
       * @return Relative X position.
       */
      float_t getRelativeX();

      /**
       * Returns the Y position, relative to this components' parent.
       * 
       * @return Relative Y position.
       */
      float_t getRelativeY();

      /**
       * Gets the game that this UI component belongs to.
       * 
       * @return Game instance.
       */
      DawnGame * getGame();

      /**
       * Gets the scene that thsi UI Component belongs to.
       * 
       * @return Scene instance.
       */
      Scene * getScene();

      /**
       * Updates the transformation for this component.
       * 
       * @param xAlign X axis alignment method.
       * @param yAlign Y axis alignment method.
       * @param alignment Alignment parameters, changes depending on the align.
       * @param z Z position (relative to screen).
       */
      virtual void setTransform(
        UIComponentAlign xAlign,
        UIComponentAlign yAlign,
        glm::vec4 alignment,
        float_t z
      );

      /**
       * Returns the list of renderable shader pass items for this UI element.
       * This is basically how you get your UI item to draw, this is called by
       * the RenderPipeline.
       * 
       * @param projection Projection matrix of the camera.
       * @param view View matrix of the camera.
       * @param parent Matrix of the parent of this UI item.
       * @return The list of shader pass items, including children.
       */
      std::vector<struct ShaderPassItem> getPassItems(
        glm::mat4 projection,
        glm::mat4 view,
        glm::mat4 parent
      );

      /**
       * Adds a child to this UI Component.
       * 
       * @param child Child UI Component to add.
       */
      virtual void addChild(UIComponent *child);

      /**
       * Removes a child from this UI Component.
       * 
       * @param child Child to remove.
       */
      virtual void removeChild(UIComponent *child);
      
      virtual ~UIComponent();
      
      friend class UICanvas;
      friend class UIGrid;
  };
}