/****************************************************************************
 * This file is part of the project AqDiagram.
 * AqDiagram (c) by 2025 Martin Preuss, all rights reserved.
 *
 * The license for this file can be found in the file COPYING which you
 * should have received along with this file.
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "./w_axis_p.h"
#include "aqdiagram/draw/w_drawable.h"
#include "aqdiagram/draw/w_label.h"

#include <gwenhywfar/debug.h>



/* ------------------------------------------------------------------------------------------------
 * defines
 * ------------------------------------------------------------------------------------------------
 */

#define AQDG_AXISWIDGET_SCALESIZE 10



/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */

static GWENHYWFAR_CB void _freeData(void *bp, void *p);

static void _setupObjectTree(AQDG_OBJECT *o);
static void _createTickLabelsForLevel(AQDG_OBJECT *o, AQDG_GRAPH *graph, const AQDG_GRAPH_TICK_LIST *tickList, int level);
static int _doesCollideWithOtherChildren(const AQDG_OBJECT *object, const AQDG_OBJECT *newObject);
static int _fitsIntoAxisWidget(const AQDG_OBJECT *o, AQDG_OBJECT *child);
static int _hasLabelCollisions(const AQDG_OBJECT *o);
static void _hideNonFittingLabels(const AQDG_OBJECT *o);
static int _hideEverySecondLabel(AQDG_OBJECT *o);



/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */

GWEN_INHERIT(AQDG_OBJECT, AQDG_WIDGET_AXIS);



AQDG_OBJECT *AQDG_AxisWidget_new(AQDG_OBJECT *parent, uint32_t options, AQDG_OBJECT *graphObject, int axisIndex)
{
  AQDG_OBJECT *o;
  AQDG_WIDGET_AXIS *xo;

  o=AQDG_DrawableWidget_new(parent, options, AQDG_DrawableWidget_GetDrawContext(graphObject));
  AQDG_Object_SetName(o, "AxisWidget");
  GWEN_NEW_OBJECT(AQDG_WIDGET_AXIS, xo);
  GWEN_INHERIT_SETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o, xo, _freeData);

  xo->graphObject=graphObject;
  xo->axisIndex=axisIndex;

  /* set defaults */
  xo->scaleSize=AQDG_AXISWIDGET_SCALESIZE;

  AQDG_DrawableWidget_SetBackgroundPenId(o, AQDG_GraphWidget_GetPen(graphObject, AQDG_GRAPHWIDGET_PEN_IDX_BACKGROUND));
  AQDG_DrawableWidget_SetForegroundPenId(o, AQDG_GraphWidget_GetPen(graphObject, AQDG_GRAPHWIDGET_PEN_IDX_AXISLINE));
  AQDG_DrawableWidget_SetFontId(o, AQDG_GraphWidget_GetFont(graphObject, AQDG_GRAPHWIDGET_FONT_IDX_AXISLABEL));

  _setupObjectTree(o);

  return o;
}




GWENHYWFAR_CB void _freeData(void *bp, void *p)
{
  AQDG_WIDGET_AXIS *xo;

  xo=(AQDG_WIDGET_AXIS*) p;

  GWEN_FREE_OBJECT(xo);
}



AQDG_OBJECT *AQDG_AxisWidget_GetGraphObject(const AQDG_OBJECT *o)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    return xo?xo->graphObject:NULL;
  }
  return NULL;
}



int AQDG_AxisWidget_GetAxisIndex(const AQDG_OBJECT *o)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    return xo?xo->axisIndex:-1;
  }
  return -1;
}


double AQDG_AxisWidget_GetMinValue(const AQDG_OBJECT *o)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    return xo?xo->minValue:0.0;
  }
  return 0.0;
}



void AQDG_AxisWidget_SetMinValue(AQDG_OBJECT *o, double v)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    if (xo)
      xo->minValue=v;
  }
}



double AQDG_AxisWidget_GetMaxValue(const AQDG_OBJECT *o)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    return xo?xo->maxValue:0.0;
  }
  return 0.0;
}



void AQDG_AxisWidget_SetMaxValue(AQDG_OBJECT *o, double v)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    if (xo)
      xo->maxValue=v;
  }
}



int AQDG_AxisWidget_GetScaleSize(const AQDG_OBJECT *o)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    return xo?xo->scaleSize:0;
  }
  return 0;
}



void AQDG_AxisWidget_SetScaleSize(AQDG_OBJECT *o, int i)
{
  if (o) {
    AQDG_WIDGET_AXIS *xo;

    xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
    if (xo)
      xo->scaleSize=i;
  }
}



void AQDG_AxisWidget_HideCollidingChildren(AQDG_OBJECT *o)
{
  int doRun=1;

  _hideNonFittingLabels(o);

  while(doRun) {
    if (_hasLabelCollisions(o))
      doRun=(_hideEverySecondLabel(o)<0)?0:1;
    else
      doRun=0;
  }
}





void _setupObjectTree(AQDG_OBJECT *o)
{
  AQDG_WIDGET_AXIS *xo;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);
  if (xo) {
    AQDG_GRAPH *graph;
    AQDG_GRAPH_AXIS *axis;

    graph=AQDG_GraphWidget_GetGraph(xo->graphObject);
    axis=AQDG_Graph_GetAxisByIndex(graph, xo->axisIndex);
    if (axis) {
      const AQDG_GRAPH_TICK_LIST *tickList;

      xo->minValue=AQDG_Graph_Axis_GetMinValue(axis);
      xo->maxValue=AQDG_Graph_Axis_GetMaxValue(axis);

      tickList=AQDG_Graph_Axis_GetTickList(axis);
      if (tickList)
	_createTickLabelsForLevel(o, graph, tickList, 0);
    }
  }
}



void _createTickLabelsForLevel(AQDG_OBJECT *o, AQDG_GRAPH *graph, const AQDG_GRAPH_TICK_LIST *tickList, int level)
{
  AQDG_WIDGET_AXIS *xo;
  const AQDG_GRAPH_TICK *tick;
  AQDG_DRAW_CONTEXT *dc;
  uint32_t opts=0;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_AXIS, o);

  if (xo->axisIndex==AQDG_GRAPH_AXISPOS_TOP)
    opts=AQDG_DRAWABLE_OPTIONS_JUSTIFY_BOTTOM;
  else if (xo->axisIndex==AQDG_GRAPH_AXISPOS_LEFT)
    opts=AQDG_DRAWABLE_OPTIONS_JUSTIFY_RIGHT;

  dc=AQDG_DrawableWidget_GetDrawContext(o);

  tick=AQDG_Graph_Tick_List_First(tickList);
  while(tick) {
    if (AQDG_Graph_Tick_GetLevel(tick)==level) {
      const char *s;

      s=AQDG_Graph_Tick_GetLabel(tick);
      if (s && *s) {
        AQDG_OBJECT *oNew;

        oNew=AQDG_LabelWidget_new(o, opts, dc, s);
        AQDG_DrawableWidget_SetValue(oNew, AQDG_Graph_Tick_GetValue(tick));
        AQDG_DrawableWidget_SetBackgroundPenId(oNew, AQDG_GraphWidget_GetPen(xo->graphObject, AQDG_GRAPHWIDGET_PEN_IDX_BACKGROUND));
        AQDG_DrawableWidget_SetForegroundPenId(oNew, AQDG_GraphWidget_GetPen(xo->graphObject, AQDG_GRAPHWIDGET_PEN_IDX_AXISLABEL));
        AQDG_DrawableWidget_SetFontId(oNew, AQDG_GraphWidget_GetFont(xo->graphObject, AQDG_GRAPHWIDGET_FONT_IDX_AXISLABEL));
      }
    }
  
    tick=AQDG_Graph_Tick_List_Next(tick);
  }
}



/*
 * TODO:
 * - set positions of all children according to value factor and offset
 * - loop:
 *   - for every child at level 0: _doesCollideWithChildren() positive on any?
 *     - yes:
 *       - remove every 2nd child, goto loop
 *     - no: okay, done
 *
 * - YAxis:
 *   - calc: pos= height - ( (value-minValue) * (height / (maxValue-minValue) ) )
 */



int _doesCollideWithOtherChildren(const AQDG_OBJECT *object, const AQDG_OBJECT *newObject)
{
  AQDG_OBJECT *child;
  int xLeft;
  int xRight;
  int yTop;
  int yBottom;

  xLeft=AQDG_Object_GetRelativeX(newObject);
  xRight=xLeft+AQDG_Object_GetWidth(newObject);
  yTop=AQDG_Object_GetRelativeY(newObject);
  yBottom=yTop+AQDG_Object_GetHeight(newObject);

  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    if (child!=newObject) {
      if (!(AQDG_Object_GetFlags(child) & AQDG_OBJECT_FLAGS_HIDDEN)) {
        int xChildLeft;
        int xChildRight;
        int yChildTop;
        int yChildBottom;

        xChildLeft=AQDG_Object_GetRelativeX(child);
        yChildTop=AQDG_Object_GetRelativeY(child);
        xChildRight=xChildLeft+AQDG_Object_GetWidth(child);
        yChildBottom=yChildTop+AQDG_Object_GetHeight(child);

        if (!
            (
             (xRight<=xChildLeft) || /* new object completely is left of child */
             (yBottom<=yChildTop) || /* new object completely is above child */
             (xLeft>=xChildRight) || /* new object is completely right of child */
             (yTop>=yChildBottom)    /* new object is completely below child */
            )
           )
          return 1;
      }
    }
    child=AQDG_Object_Tree2_GetNext(child);
  }

  return 0;
}



int _fitsIntoAxisWidget(const AQDG_OBJECT *o, AQDG_OBJECT *child)
{
  int xLeft;
  int xRight;
  int yTop;
  int yBottom;
  int parentWidth;
  int parentHeight;

  parentWidth=AQDG_Object_GetWidth(o);
  parentHeight=AQDG_Object_GetHeight(o);
  xLeft=AQDG_Object_GetRelativeX(child);
  xRight=xLeft+AQDG_Object_GetWidth(child);
  yTop=AQDG_Object_GetRelativeY(child);
  yBottom=yTop+AQDG_Object_GetHeight(child);

  return ((xLeft>=0) && (xRight<parentWidth) && (yTop>=0) && (yBottom<parentHeight))?1:0;
}



int _hasLabelCollisions(const AQDG_OBJECT *o)
{
  AQDG_OBJECT *child;

  child=AQDG_Object_Tree2_GetFirstChild(o);
  while(child) {
    if (!(AQDG_Object_GetFlags(child) & AQDG_OBJECT_FLAGS_HIDDEN)) {
      if (_doesCollideWithOtherChildren(o, child))
        return 1;
    }
    child=AQDG_Object_Tree2_GetNext(child);
  }
  return 0;
}



void _hideNonFittingLabels(const AQDG_OBJECT *o)
{
  AQDG_OBJECT *child;

  child=AQDG_Object_Tree2_GetFirstChild(o);
  while(child) {
    if (!(AQDG_Object_GetFlags(child) & AQDG_OBJECT_FLAGS_HIDDEN)) {
      if (!_fitsIntoAxisWidget(o, child))
	AQDG_Object_AddFlags(child, AQDG_OBJECT_FLAGS_HIDDEN);
    }
    child=AQDG_Object_Tree2_GetNext(child);
  }
}



int _hideEverySecondLabel(AQDG_OBJECT *o)
{
  AQDG_OBJECT *child;
  int hadVisibleChildren=0;

  child=AQDG_DrawableWidget_GetFirstVisibleChild(o);
  while(child) {
    child=AQDG_DrawableWidget_GetNextVisibleWidget(child);  /* skip current */
    if (child) {
      hadVisibleChildren=1;
      AQDG_Object_AddFlags(child, AQDG_OBJECT_FLAGS_HIDDEN);
    }
    child=AQDG_DrawableWidget_GetNextVisibleWidget(child);
  }
  return hadVisibleChildren?0:GWEN_ERROR_NOT_FOUND;
}





