/****************************************************************************
 * This file is part of the project AqDiagram.
 * AqDiagram (c) by 2024 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_yaxis.h"
#include "./w_axis.h"
#include "aqdiagram/draw/w_drawable.h"
#include "aqdiagram/placement/o_layout.h"

#include <gwenhywfar/debug.h>



/* ------------------------------------------------------------------------------------------------
 * definitions
 * ------------------------------------------------------------------------------------------------
 */

#define AQDG_WIDGET_YAXIS_TICK_MIN_Y 20



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

static int _getDefaultWidth(AQDG_OBJECT *o);
static int _getDefaultHeight(AQDG_OBJECT *o);
static int _layout(AQDG_OBJECT *object);
static int _draw(AQDG_OBJECT *object);
static void _drawTicks(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, int x);
static int _screenDistBetweenLevelTicks(const AQDG_GRAPH_TICK_LIST *tickList, int lvl,
                                        int contentSize, double minValue, double maxValue);
static void _setChildrenRelX(AQDG_OBJECT *o);
static void _setChildrenRelYFromValue(AQDG_OBJECT *o);
static void _setChildrenSizes(AQDG_OBJECT *o);
static int _calcVerticalPos(double value, int contentSize, double minValue, double maxValue);



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

AQDG_OBJECT *AQDG_YAxisWidget_new(AQDG_OBJECT *parent, uint32_t options, AQDG_OBJECT *graphObject, int axisIndex)
{
  AQDG_OBJECT *o;

  o=AQDG_AxisWidget_new(parent, options, graphObject, axisIndex);
  AQDG_Object_SetName(o, "YAxisWidget");

  AQDG_Object_SetLayoutFn(o, _layout);
  AQDG_DrawableWidget_SetDrawFn(o, _draw);
  AQDG_Object_SetGetDefaultWidthFn(o, _getDefaultWidth);
  AQDG_Object_SetGetDefaultHeightFn(o, _getDefaultHeight);
  AQDG_Object_SetBorderTop(o, 0);
  AQDG_Object_SetBorderBottom(o, 0);

  return o;
}



int _getDefaultWidth(AQDG_OBJECT *o)
{
  AQDG_OBJECT *child;
  int w=0;

  /* get max label width */
  child=AQDG_Object_Tree2_GetFirstChild(o);
  while(child) {
    int i;

    i=AQDG_Object_GetDefaultWidth(child);
    w=(i>w)?i:w;
    child=AQDG_Object_Tree2_GetNext(child);
  }

  /* add space for scale, left border and right border */
  w+=AQDG_AxisWidget_GetScaleSize(o);
  w+=AQDG_Object_GetBorderLeft(o)+AQDG_Object_GetBorderRight(o);
  return w;
}



int _getDefaultHeight(AQDG_OBJECT *o)
{
  return 1;
}



int _layout(AQDG_OBJECT *o)
{
  _setChildrenSizes(o);
  _setChildrenRelX(o);
  _setChildrenRelYFromValue(o);
  AQDG_DrawableWidget_UnhideAllChildren(o);
  AQDG_AxisWidget_HideCollidingChildren(o);
  AQDG_LayoutObject_LayoutChildren(o);
  return 1;
}



int _draw(AQDG_OBJECT *object)
{
  AQDG_DRAW_CONTEXT *dc;
  int x;
  int y1;
  int y2;
  int pen;

  AQDG_DrawableWidget_DefaultDraw(object);

  dc=AQDG_DrawableWidget_GetDrawContext(object);
  x=AQDG_Object_GetAbsoluteX(object);
  x+=AQDG_Object_GetWidth(object)-AQDG_Object_GetBorderRight(object);
  y1=AQDG_Object_GetAbsoluteY(object)+AQDG_Object_GetBorderTop(object);
  y2=AQDG_Object_GetAbsoluteY(object)+AQDG_Object_GetHeight(object)-AQDG_Object_GetBorderBottom(object);
  pen=AQDG_DrawableWidget_GetForegroundPenId(object);
  AQDG_Draw_Context_DrawLine(dc, pen, x, y1, x, y2);
  _drawTicks(object, dc, x);

  return 1;
}



void _drawTicks(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, int absX)
{
  AQDG_OBJECT *graphObject;
  AQDG_GRAPH *graph;
  AQDG_GRAPH_AXIS *axis;

  graphObject=AQDG_AxisWidget_GetGraphObject(o);
  graph=AQDG_GraphWidget_GetGraph(graphObject);
  axis=AQDG_Graph_GetAxisByIndex(graph, AQDG_AxisWidget_GetAxisIndex(o));
  if (axis) {
    const AQDG_GRAPH_TICK_LIST *tickList;

    tickList=AQDG_Graph_Axis_GetTickList(axis);
    if (tickList) {
      const AQDG_GRAPH_TICK *tick;
      int contentSize;
      int scaleSize;
      double minValue;
      double maxValue;
      int absY;
      int pen;
      int showLevel1;
      int showLevel2;

      contentSize=AQDG_Object_GetHeight(o)-AQDG_Object_GetBorderTop(o)-AQDG_Object_GetBorderBottom(o);
      scaleSize=AQDG_AxisWidget_GetScaleSize(o);
      absY=AQDG_Object_GetAbsoluteY(o);
      minValue=AQDG_AxisWidget_GetMinValue(o);
      maxValue=AQDG_AxisWidget_GetMaxValue(o);
      pen=AQDG_DrawableWidget_GetForegroundPenId(o);
      showLevel1=(_screenDistBetweenLevelTicks(tickList, 1, contentSize, minValue, maxValue)>AQDG_WIDGET_YAXIS_TICK_MIN_Y)?1:0;
      showLevel2=(_screenDistBetweenLevelTicks(tickList, 2, contentSize, minValue, maxValue)>AQDG_WIDGET_YAXIS_TICK_MIN_Y)?1:0;

      tick=AQDG_Graph_Tick_List_First(tickList);
      while(tick) {
        double value;
        int ypos;
        int tickLevel;

        value=AQDG_Graph_Tick_GetValue(tick);
        ypos=_calcVerticalPos(value, contentSize, minValue, maxValue);
        ypos+=AQDG_Object_GetBorderTop(o);
        tickLevel=AQDG_Graph_Tick_GetLevel(tick);
        if (tickLevel==0)
          AQDG_Draw_Context_DrawLine(dc, pen, absX-(scaleSize/2), absY+ypos, absX+(scaleSize/2), absY+ypos);
        else if ((tickLevel==1 && showLevel1) || ((tickLevel==2 && showLevel2))) {
          AQDG_Draw_Context_DrawLine(dc, pen, absX-(scaleSize/4), absY+ypos, absX+(scaleSize/4), absY+ypos);
        }
        tick=AQDG_Graph_Tick_List_Next(tick);
      }
    }
  }
}



int _screenDistBetweenLevelTicks(const AQDG_GRAPH_TICK_LIST *tickList, int lvl,
                                 int contentSize, double minValue, double maxValue)
{
  const AQDG_GRAPH_TICK *firstLevelTick;

  firstLevelTick=AQDG_Graph_Tick_List_GetFirstTickForLevel(tickList, lvl);
  if (firstLevelTick) {
    double v;
    int pts;

    v=AQDG_Graph_Tick_GetLevelDist(firstLevelTick);
    pts=(v*(contentSize/(maxValue-minValue)));
    DBG_ERROR(NULL, "Level dist %d: %d", lvl, pts);
    return pts;
  }
  return 0;
}



void _setChildrenRelX(AQDG_OBJECT *o)
{
  int contentSize;

  contentSize=AQDG_Object_GetWidth(o);

  if (AQDG_AxisWidget_GetAxisIndex(o)==AQDG_GRAPH_AXISPOS_RIGHT) {
    AQDG_OBJECT *child;
    int borderSize;

    borderSize=AQDG_Object_GetBorderLeft(o)+AQDG_AxisWidget_GetScaleSize(o);
    /* align left */
    child=AQDG_Object_Tree2_GetFirstChild(o);
    while(child) {
      AQDG_Object_SetRelativeX(child, borderSize);
      child=AQDG_Object_Tree2_GetNext(child);
    }
  }
  else {
    AQDG_OBJECT *child;
    int borderSize;

    /* align right */
    borderSize=AQDG_Object_GetBorderRight(o)+AQDG_AxisWidget_GetScaleSize(o);
    child=AQDG_Object_Tree2_GetFirstChild(o);
    while(child) {
      int pos;
      int cwidth;

      cwidth=AQDG_Object_GetWidth(child);
      pos=contentSize-cwidth-borderSize;
      AQDG_Object_SetRelativeX(child, pos);
      child=AQDG_Object_Tree2_GetNext(child);
    }
  }
}



void _setChildrenRelYFromValue(AQDG_OBJECT *o)
{
  AQDG_OBJECT *child;
  int contentSize;
  double minValue;
  double maxValue;

  contentSize=AQDG_Object_GetHeight(o)-AQDG_Object_GetBorderTop(o)-AQDG_Object_GetBorderBottom(o);
  minValue=AQDG_AxisWidget_GetMinValue(o);
  maxValue=AQDG_AxisWidget_GetMaxValue(o);

  child=AQDG_Object_Tree2_GetFirstChild(o);
  while(child) {
    int pos;
    double value;

    value=AQDG_DrawableWidget_GetValue(child);
    pos=_calcVerticalPos(value, contentSize, minValue, maxValue);
    pos+=AQDG_Object_GetBorderTop(o);
    pos-=AQDG_Object_GetHeight(child)/2;
    AQDG_Object_SetRelativeY(child, pos); /* TODO: take label size into account */
    child=AQDG_Object_Tree2_GetNext(child);
  }
}



void _setChildrenSizes(AQDG_OBJECT *o)
{
  AQDG_OBJECT *child;

  child=AQDG_Object_Tree2_GetFirstChild(o);
  while(child) {
    AQDG_Object_SetWidth(child, AQDG_Object_GetDefaultWidth(child));
    AQDG_Object_SetHeight(child, AQDG_Object_GetDefaultHeight(child));
    child=AQDG_Object_Tree2_GetNext(child);
  }
}



/**
 * special case because 0/0 is top left corner, so we need to flip contentSize
 */
int _calcVerticalPos(double value, int contentSize, double minValue, double maxValue)
{
  return (contentSize-((value-minValue)*(contentSize/(maxValue-minValue))));
}




