/****************************************************************************
 * 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_viewport_p.h"
#include "./w_axis.h"
#include "aqdiagram/draw/w_drawable.h"

#include <gwenhywfar/debug.h>



#define AQDG_WIDGET_VIEWPORT_TICK_MIN_X 10
#define AQDG_WIDGET_VIEWPORT_TICK_MIN_Y 20



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

static GWENHYWFAR_CB void _freeData(void *bp, void *p);
static int _draw(AQDG_OBJECT *object);
static void _drawCurves(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc);
static void _drawCurve(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                       double minValueX, double maxValueX, double minValueY, double maxValueY,
                       int pen);
static void _drawVerticalGrid(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc);
static void _drawHorizontalGrid(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc);
static int _screenDistBetweenLevelTicksX(const AQDG_GRAPH_TICK_LIST *tickList, int lvl,
                                         int contentSize, double minValue, double maxValue);
static int _screenDistBetweenLevelTicksY(const AQDG_GRAPH_TICK_LIST *tickList, int lvl,
                                         int contentSize, double minValue, double maxValue);
static int _distToScreenDist(int contentSize, double minValue, double maxValue, double value);

static void _drawCurveLines(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                            double minValueX, double maxValueX, double minValueY, double maxValueY,
                            int pen);
static void _drawCurvePoints(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                             double minValueX, double maxValueX, double minValueY, double maxValueY,
                             int pen);
static void _drawCurveStepLines(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                                double minValueX, double maxValueX, double minValueY, double maxValueY,
                                int pen);
static void _drawCurveVertBars(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
			       double minValueX, double maxValueX, double minValueY, double maxValueY,
			       int pen);



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

GWEN_INHERIT(AQDG_OBJECT, AQDG_WIDGET_VIEWPORT);



AQDG_OBJECT *AQDG_ViewportWidget_new(AQDG_OBJECT *parent, uint32_t options, AQDG_OBJECT *graphObject)
{
  AQDG_OBJECT *o;
  AQDG_WIDGET_VIEWPORT *xo;

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

  AQDG_DrawableWidget_SetDrawFn(o, _draw);

  xo->graphObject=graphObject;

  AQDG_DrawableWidget_SetBackgroundPenId(o, AQDG_GraphWidget_GetPen(graphObject, AQDG_GRAPHWIDGET_PEN_IDX_GRAPHBACKGROUND));
  AQDG_Object_SetBorderLeft(o, 0);
  AQDG_Object_SetBorderRight(o, 0);
  AQDG_Object_SetBorderTop(o, 0);
  AQDG_Object_SetBorderBottom(o, 0);

  return o;
}




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

  xo=(AQDG_WIDGET_VIEWPORT*) p;

  GWEN_FREE_OBJECT(xo);
}



int _draw(AQDG_OBJECT *object)
{
  AQDG_DRAW_CONTEXT *dc;

  AQDG_DrawableWidget_DefaultDraw(object);

  dc=AQDG_DrawableWidget_GetDrawContext(object);
  _drawVerticalGrid(object, dc);
  _drawHorizontalGrid(object, dc);
  _drawCurves(object, dc);
  return 1;
}



int _calcHorizontalPos(double value, int contentSize, double minValue, double maxValue)
{
  return ((value-minValue)*(contentSize/(maxValue-minValue)));
}



int _calcVerticalPos(double value, int contentSize, double minValue, double maxValue)
{
  return (contentSize-((value-minValue)*(contentSize/(maxValue-minValue))));
}



void _drawCurves(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc)
{
  AQDG_WIDGET_VIEWPORT *xo;
  AQDG_GRAPH *graph;
  const AQDG_GRAPH_SUBGRAPH_LIST *subGraphList;
  const AQDG_GRAPH_SUBGRAPH *subGraph;
  int penIdx=AQDG_GRAPHWIDGET_PEN_IDX_CURVE0;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_VIEWPORT, o);
  graph=AQDG_GraphWidget_GetGraph(xo->graphObject);

  subGraphList=AQDG_Graph_GetSubGraphList(graph);
  subGraph=subGraphList?AQDG_Graph_SubGraph_List_First(subGraphList):NULL;
  while (subGraph) {
    const AQDG_GRAPH_AXIS *xAxis;
    const AQDG_GRAPH_AXIS *yAxis;
    double minValueX;
    double maxValueX;
    double minValueY;
    double maxValueY;
    const AQDG_GRAPH_CURVE_LIST *curveList;
    const AQDG_GRAPH_CURVE *curve;

    xAxis=AQDG_Graph_GetAxisByIndex(graph, AQDG_Graph_SubGraph_GetIndexAxisX(subGraph));
    yAxis=AQDG_Graph_GetAxisByIndex(graph, AQDG_Graph_SubGraph_GetIndexAxisY(subGraph));
    minValueX=AQDG_Graph_Axis_GetMinValue(xAxis);
    maxValueX=AQDG_Graph_Axis_GetMaxValue(xAxis);
    minValueY=AQDG_Graph_Axis_GetMinValue(yAxis);
    maxValueY=AQDG_Graph_Axis_GetMaxValue(yAxis);

    curveList=AQDG_Graph_SubGraph_GetCurves(subGraph);
    curve=curveList?AQDG_Graph_Curve_List_First(curveList):NULL;
    while(curve) {
      int pen;

      pen=AQDG_GraphWidget_GetPen(xo->graphObject, penIdx);
      _drawCurve(o, dc, curve, minValueX, maxValueX, minValueY, maxValueY, pen);

      penIdx++;
      if (penIdx>=AQDG_GRAPHWIDGET_PEN_IDX_LAST)
        penIdx=AQDG_GRAPHWIDGET_PEN_IDX_CURVE0;
      curve=AQDG_Graph_Curve_List_Next(curve);
    }

    subGraph=AQDG_Graph_SubGraph_List_Next(subGraph);
  }
}



void _drawCurve(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                double minValueX, double maxValueX, double minValueY, double maxValueY,
                int pen)
{
  switch(AQDG_Graph_Curve_GetGraphType(curve)) {
  case AQDG_GRAPH_TYPE_LINE:
    _drawCurveLines(o, dc, curve, minValueX, maxValueX, minValueY, maxValueY, pen);
    break;
  case AQDG_GRAPH_TYPE_POINTS:
    _drawCurvePoints(o, dc, curve, minValueX, maxValueX, minValueY, maxValueY, pen);
    break;
  case AQDG_GRAPH_TYPE_STEPLINE:
    _drawCurveStepLines(o, dc, curve, minValueX, maxValueX, minValueY, maxValueY, pen);
    break;
  case AQDG_GRAPH_TYPE_BARS:
    _drawCurveVertBars(o, dc, curve, minValueX, maxValueX, minValueY, maxValueY, pen);
    break;
  default:
    break;
  }
}



void _drawVerticalGrid(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc)
{
  AQDG_WIDGET_VIEWPORT *xo;
  AQDG_GRAPH *graph;
  AQDG_GRAPH_AXIS *axis;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_VIEWPORT, o);
  graph=AQDG_GraphWidget_GetGraph(xo->graphObject);
  axis=AQDG_Graph_GetAxisByIndex(graph, AQDG_GRAPH_AXISPOS_BOTTOM);
  if (axis==NULL)
    axis=AQDG_Graph_GetAxisByIndex(graph, AQDG_GRAPH_AXISPOS_TOP);
  if (axis) {
    const AQDG_GRAPH_TICK_LIST *tickList;

    tickList=AQDG_Graph_Axis_GetTickList(axis);
    if (tickList) {
      const AQDG_GRAPH_TICK *tick;
      int contentSize;
      double minValue;
      double maxValue;
      int absX;
      int pen1;
      int pen2;
      int y1;
      int y2;
      int showLevel1;
      int showLevel2;

      y1=AQDG_Object_GetAbsoluteY(o)+AQDG_Object_GetBorderTop(o);
      y2=AQDG_Object_GetAbsoluteY(o)+AQDG_Object_GetHeight(o)-AQDG_Object_GetBorderBottom(o);
      contentSize=AQDG_Object_GetWidth(o)-AQDG_Object_GetBorderLeft(o)-AQDG_Object_GetBorderRight(o);
      absX=AQDG_Object_GetAbsoluteX(o);
      minValue=AQDG_Graph_Axis_GetMinValue(axis);
      maxValue=AQDG_Graph_Axis_GetMaxValue(axis);
      pen1=AQDG_GraphWidget_GetPen(xo->graphObject, AQDG_GRAPHWIDGET_PEN_IDX_GRID1);
      pen2=AQDG_GraphWidget_GetPen(xo->graphObject, AQDG_GRAPHWIDGET_PEN_IDX_GRID2);
      showLevel1=(_screenDistBetweenLevelTicksX(tickList, 1, contentSize, minValue, maxValue)>AQDG_WIDGET_VIEWPORT_TICK_MIN_X)?1:0;
      showLevel2=(_screenDistBetweenLevelTicksY(tickList, 2, contentSize, minValue, maxValue)>AQDG_WIDGET_VIEWPORT_TICK_MIN_X)?1:0;

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

        value=AQDG_Graph_Tick_GetValue(tick);
        xpos=_calcHorizontalPos(value, contentSize, minValue, maxValue);
        xpos+=AQDG_Object_GetBorderLeft(o);
        tickLevel=AQDG_Graph_Tick_GetLevel(tick);
        if (tickLevel==0)
          AQDG_Draw_Context_DrawLine(dc, pen1, absX+xpos, y1, absX+xpos, y2);
        else if ((tickLevel==1 && showLevel1) || ((tickLevel==2 && showLevel2))) {
          AQDG_Draw_Context_DrawLine(dc, pen2, absX+xpos, y1, absX+xpos, y2);
        }
        tick=AQDG_Graph_Tick_List_Next(tick);
      }
    }
  }
}



void _drawHorizontalGrid(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc)
{
  AQDG_WIDGET_VIEWPORT *xo;
  AQDG_GRAPH *graph;
  AQDG_GRAPH_AXIS *axis;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_VIEWPORT, o);
  graph=AQDG_GraphWidget_GetGraph(xo->graphObject);
  axis=AQDG_Graph_GetAxisByIndex(graph, AQDG_GRAPH_AXISPOS_LEFT);
  if (axis==NULL)
    axis=AQDG_Graph_GetAxisByIndex(graph, AQDG_GRAPH_AXISPOS_RIGHT);
  if (axis) {
    const AQDG_GRAPH_TICK_LIST *tickList;

    tickList=AQDG_Graph_Axis_GetTickList(axis);
    if (tickList) {
      const AQDG_GRAPH_TICK *tick;
      int contentSize;
      double minValue;
      double maxValue;
      int absY;
      int pen1;
      int pen2;
      int x1;
      int x2;
      int showLevel1;
      int showLevel2;

      x1=AQDG_Object_GetAbsoluteX(o)+AQDG_Object_GetBorderLeft(o);
      x2=AQDG_Object_GetAbsoluteX(o)+AQDG_Object_GetWidth(o)-AQDG_Object_GetBorderLeft(o)-AQDG_Object_GetBorderRight(o);
      contentSize=AQDG_Object_GetHeight(o)-AQDG_Object_GetBorderTop(o)-AQDG_Object_GetBorderBottom(o);
      absY=AQDG_Object_GetAbsoluteY(o);
      minValue=AQDG_Graph_Axis_GetMinValue(axis);
      maxValue=AQDG_Graph_Axis_GetMaxValue(axis);
      pen1=AQDG_GraphWidget_GetPen(xo->graphObject, AQDG_GRAPHWIDGET_PEN_IDX_GRID1);
      pen2=AQDG_GraphWidget_GetPen(xo->graphObject, AQDG_GRAPHWIDGET_PEN_IDX_GRID2);
      showLevel1=(_screenDistBetweenLevelTicksY(tickList, 1, contentSize, minValue, maxValue)>AQDG_WIDGET_VIEWPORT_TICK_MIN_Y)?1:0;
      showLevel2=(_screenDistBetweenLevelTicksY(tickList, 2, contentSize, minValue, maxValue)>AQDG_WIDGET_VIEWPORT_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, pen1, x1, absY+ypos, x2, absY+ypos);
        else if ((tickLevel==1 && showLevel1) || ((tickLevel==2 && showLevel2))) {
          AQDG_Draw_Context_DrawLine(dc, pen2, x1, absY+ypos, x2, absY+ypos);
        }
        tick=AQDG_Graph_Tick_List_Next(tick);
      }
    }
  }
}



int _screenDistBetweenLevelTicksX(const AQDG_GRAPH_TICK_LIST *tickList, int lvl,
                                 int contentSize, double minValue, double maxValue)
{
  return _distToScreenDist(contentSize, minValue, maxValue, AQDG_Graph_Tick_List_DistanceBetweenTicks(tickList, lvl));
}



int _screenDistBetweenLevelTicksY(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)
    return _distToScreenDist(contentSize, minValue, maxValue, AQDG_Graph_Tick_GetLevelDist(firstLevelTick));
  return 0;
}



int _distToScreenDist(int contentSize, double minValue, double maxValue, double value)
{
  int pts;

  pts=(value*(contentSize/(maxValue-minValue)));
  return pts;
}






/* ------------------------------------------------------------------------------------------------
 * graph drawing
 * ------------------------------------------------------------------------------------------------
 */



void _drawCurveLines(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                     double minValueX, double maxValueX, double minValueY, double maxValueY,
                     int pen)
{
  int width;
  int height;
  const AQDG_GRAPH_DATAPAIR_LIST *dpList;
  const AQDG_GRAPH_DATAPAIR *dp;
  int absX;
  int absY;
  int lastX=-1;
  int lastY=-1;

  absX=AQDG_Object_GetAbsoluteX(o);
  absY=AQDG_Object_GetAbsoluteY(o);
  width=AQDG_Object_GetWidth(o);
  height=AQDG_Object_GetHeight(o);
  dpList=AQDG_Graph_Curve_GetDataPairs(curve);
  dp=dpList?AQDG_Graph_DataPair_List_First(dpList):NULL;
  while(dp) {
    int xPos;
    int yPos;
    double xValue;
    double yValue;

    xValue=AQDG_Graph_DataPair_GetValueX(dp);
    yValue=AQDG_Graph_DataPair_GetValueY(dp);

    xPos=_calcHorizontalPos(xValue, width, minValueX, maxValueX);
    yPos=_calcVerticalPos(yValue, height, minValueY, maxValueY);
    if (lastX>=0 && lastY>=0)
      AQDG_Draw_Context_DrawLine(dc, pen, absX+lastX, absY+lastY, absX+xPos, absY+yPos);
    AQDG_Draw_Context_DrawLine(dc, pen, absX+xPos-2, absY+yPos-2, absX+xPos+2, absY+yPos+2);
    AQDG_Draw_Context_DrawLine(dc, pen, absX+xPos-2, absY+yPos+2, absX+xPos+2, absY+yPos-2);
    lastX=xPos;
    lastY=yPos;
    dp=AQDG_Graph_DataPair_List_Next(dp);
  }
}



void _drawCurvePoints(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                      double minValueX, double maxValueX, double minValueY, double maxValueY,
                      int pen)
{
  int width;
  int height;
  const AQDG_GRAPH_DATAPAIR_LIST *dpList;
  const AQDG_GRAPH_DATAPAIR *dp;
  int absX;
  int absY;

  absX=AQDG_Object_GetAbsoluteX(o);
  absY=AQDG_Object_GetAbsoluteY(o);
  width=AQDG_Object_GetWidth(o);
  height=AQDG_Object_GetHeight(o);
  dpList=AQDG_Graph_Curve_GetDataPairs(curve);
  dp=dpList?AQDG_Graph_DataPair_List_First(dpList):NULL;
  while(dp) {
    int xPos;
    int yPos;
    double xValue;
    double yValue;

    xValue=AQDG_Graph_DataPair_GetValueX(dp);
    yValue=AQDG_Graph_DataPair_GetValueY(dp);

    xPos=_calcHorizontalPos(xValue, width, minValueX, maxValueX);
    yPos=_calcVerticalPos(yValue, height, minValueY, maxValueY);
    AQDG_Draw_Context_DrawLine(dc, pen, absX+xPos-4, absY+yPos-4, absX+xPos+4, absY+yPos+4);
    AQDG_Draw_Context_DrawLine(dc, pen, absX+xPos-4, absY+yPos+4, absX+xPos+4, absY+yPos-4);
    dp=AQDG_Graph_DataPair_List_Next(dp);
  }
}



void _drawCurveStepLines(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                         double minValueX, double maxValueX, double minValueY, double maxValueY,
                         int pen)
{
  int width;
  int height;
  const AQDG_GRAPH_DATAPAIR_LIST *dpList;
  const AQDG_GRAPH_DATAPAIR *dp;
  int absX;
  int absY;
  int lastX=-1;
  int lastY=-1;

  absX=AQDG_Object_GetAbsoluteX(o);
  absY=AQDG_Object_GetAbsoluteY(o);
  width=AQDG_Object_GetWidth(o);
  height=AQDG_Object_GetHeight(o);
  dpList=AQDG_Graph_Curve_GetDataPairs(curve);
  dp=dpList?AQDG_Graph_DataPair_List_First(dpList):NULL;
  while(dp) {
    int xPos;
    int yPos;
    double xValue;
    double yValue;

    xValue=AQDG_Graph_DataPair_GetValueX(dp);
    yValue=AQDG_Graph_DataPair_GetValueY(dp);

    xPos=_calcHorizontalPos(xValue, width, minValueX, maxValueX);
    yPos=_calcVerticalPos(yValue, height, minValueY, maxValueY);
    if (lastX>=0 && lastY>=0) {
      AQDG_Draw_Context_DrawLine(dc, pen, absX+lastX, absY+lastY, absX+xPos, absY+lastY);
      AQDG_Draw_Context_DrawLine(dc, pen, absX+xPos, absY+lastY, absX+xPos, absY+yPos);
    }
    AQDG_Draw_Context_DrawLine(dc, pen, absX+xPos-2, absY+yPos-2, absX+xPos+2, absY+yPos+2);
    AQDG_Draw_Context_DrawLine(dc, pen, absX+xPos-2, absY+yPos+2, absX+xPos+2, absY+yPos-2);
    lastX=xPos;
    lastY=yPos;
    dp=AQDG_Graph_DataPair_List_Next(dp);
  }
}



void _drawCurveVertBars(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve,
                        double minValueX, double maxValueX, double minValueY, double maxValueY,
                        int pen)
{
  const AQDG_GRAPH_DATAPAIR_LIST *dpList;
  const AQDG_GRAPH_DATAPAIR *dp;

  dpList=AQDG_Graph_Curve_GetDataPairs(curve);
  dp=dpList?AQDG_Graph_DataPair_List_First(dpList):NULL;
  if (dp) {
    int width;
    int height;
    int absX;
    int absY;
    double valueMinDist;
    int barWidth;
    double baseLineValueY;
    int baseLinePosY;

    absX=AQDG_Object_GetAbsoluteX(o);
    absY=AQDG_Object_GetAbsoluteY(o);
    width=AQDG_Object_GetWidth(o);
    height=AQDG_Object_GetHeight(o);
    valueMinDist=AQDG_Graph_DataPair_List_GetMinDiffX(dpList);
    barWidth=_distToScreenDist(width, minValueX, maxValueX, (valueMinDist*5.0)/6.0);

    if (maxValueY<0.0)
      baseLineValueY=maxValueY;
    else if (minValueY>0.0)
      baseLineValueY=minValueY;
    else
      baseLineValueY=0.0;
    baseLinePosY=_calcVerticalPos(baseLineValueY, height, minValueY, maxValueY);

#if 0
    DBG_ERROR(NULL,
	      "valueMinDist=%.2f, barWidth=%d, baseLineValueY=%.2f, baseLinePosY=%d",
	      valueMinDist, barWidth, baseLineValueY, baseLinePosY);
#endif
    while(dp) {
      int pos;
      int xPos1;
      int xPos2;
      int yPos;
      int h;
      double xValue;
      double yValue;

      xValue=AQDG_Graph_DataPair_GetValueX(dp);
      yValue=AQDG_Graph_DataPair_GetValueY(dp);

      pos=_calcHorizontalPos(xValue, width, minValueX, maxValueX);
      xPos1=pos-barWidth/2;
      xPos2=pos+barWidth/2;
      xPos1=(xPos1<0)?0:xPos1;
      xPos2=(width>xPos2)?xPos2:(width-1);
      yPos=_calcVerticalPos(yValue, height, minValueY, maxValueY);
      h=baseLinePosY-yPos;
      if (h<0) {
	yPos+=h;
	h=-h;
      }
      AQDG_Draw_Context_DrawFilledRect(dc, pen, absX+xPos1, absY+yPos, (xPos2-xPos1)+1, h);
      dp=AQDG_Graph_DataPair_List_Next(dp);
    }
  }
}






