/****************************************************************************
 * This file is part of the project AqDiagram.
 * AqDiagram (c) by 2023 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 "o_mlayout_p.h"
#include "o_layout.h"

#include <gwenhywfar/debug.h>



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

AQDG_OBJECT *_matrixLayoutObject_new(AQDG_OBJECT *parent, uint32_t options, int numRowsOrColumns);
static GWENHYWFAR_CB void _freeData(void *bp, void *p);
static int _layoutByRows(AQDG_OBJECT *object);
static int _layoutByColumns(AQDG_OBJECT *object);

static void _layoutSecondaryY(AQDG_OBJECT *object, int numElements);
static void _layoutSecondaryX(AQDG_OBJECT *object, int numElements);
static AQDG_PLACEMENT_LAYOUT_ELEMENT *_mkEmptyElementList(int num);

static void _mergeObjectsIntoElementsByRowX(AQDG_OBJECT *object, AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements, int numElements);
static void _mergeObjectsIntoElementsByRowY(AQDG_OBJECT *object, AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements, int numElements);

static void _mergeObjectXIntoElement(AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr, AQDG_OBJECT *object);
static void _mergeObjectYIntoElement(AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr, AQDG_OBJECT *object);

static void _setChildrenFromElementsByRowX(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *elements, int num);
static void _setChildrenFromElementsByRowY(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *elements, int num);
static void _setChildrenFromElementsY(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *elements, int num);
static void _setObjectFromElementX(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr);
static void _setObjectFromElementY(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr);

static int _getDefaultWidthByRows(AQDG_OBJECT *object);
static int _getDefaultHeightByRows(AQDG_OBJECT *object);

static int _getDefaultWidthByColumns(AQDG_OBJECT *object);
static int _getDefaultHeightByColumns(AQDG_OBJECT *object);

static int _calcWidthByRows(AQDG_OBJECT *object, int columns);
static int _calcMaxColumnWidth(AQDG_OBJECT *object, int columns);
static int _calcHeightByRows(const AQDG_OBJECT *object, int columns);

static int _calcWidthByColumns(const AQDG_OBJECT *object, int rows);
static int _calcHeightByColumns(const AQDG_OBJECT *object, int rows);



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

GWEN_INHERIT(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT);



AQDG_OBJECT *AQDG_MatrixLayoutObjectByRows_new(AQDG_OBJECT *parent, uint32_t options, int columns)
{
  AQDG_OBJECT *object;

  object=_matrixLayoutObject_new(parent, options, columns);
  AQDG_Object_SetLayoutFn(object, _layoutByRows);
  AQDG_Object_SetGetDefaultWidthFn(object, _getDefaultWidthByRows);
  AQDG_Object_SetGetDefaultHeightFn(object, _getDefaultHeightByRows);
  return object;
}



AQDG_OBJECT *AQDG_MatrixLayoutObjectByColumns_new(AQDG_OBJECT *parent, uint32_t options, int rows)
{
  AQDG_OBJECT *object;

  object=_matrixLayoutObject_new(parent, options, rows);
  AQDG_Object_SetLayoutFn(object, _layoutByColumns);
  AQDG_Object_SetGetDefaultWidthFn(object, _getDefaultWidthByColumns);
  AQDG_Object_SetGetDefaultHeightFn(object, _getDefaultHeightByColumns);
  return object;
}




AQDG_OBJECT *_matrixLayoutObject_new(AQDG_OBJECT *parent, uint32_t options, int numRowsOrColumns)
{
  AQDG_OBJECT *object;
  AQDG_OBJECT_MLAYOUT *xo;

  object=AQDG_Object_new();
  AQDG_Object_SetName(object, "MLayoutObject");
  GWEN_NEW_OBJECT(AQDG_OBJECT_MLAYOUT, xo);
  GWEN_INHERIT_SETDATA(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT, object, xo, _freeData);

  AQDG_Object_SetOptions(object, options);
  xo->numRowsOrColumns=numRowsOrColumns;

  if (parent)
    AQDG_Object_Tree2_AddChild(parent, object);
  return object;
}



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

  xo=(AQDG_OBJECT_MLAYOUT*) p;
  GWEN_FREE_OBJECT(xo);
}



int _layoutByRows(AQDG_OBJECT *object)
{
  AQDG_OBJECT_MLAYOUT *xo;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT, object);
  if (xo) {
    AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements;
    int numColumns;
    int numRows;
    int numChildren;

    numColumns=xo->numRowsOrColumns;
    numChildren=AQDG_LayoutObject_CountDirectChildren(object);
    numRows=(numChildren+numColumns)/numColumns;

    /* layout columns */
    ptrElements=_mkEmptyElementList(numColumns);
    if (ptrElements) {
      _mergeObjectsIntoElementsByRowX(object, ptrElements, numColumns);
      AQDG_Placement_LayoutAndStretch(ptrElements, numColumns,
				      AQDG_Object_GetWidth(object),
				      AQDG_Object_GetBorderLeft(object),
				      AQDG_Object_GetBorderRight(object),
				      AQDG_Object_GetHSpacing(object));
      _setChildrenFromElementsByRowX(object, ptrElements, numColumns);
      free(ptrElements);
    }

    /* layout rows */
    ptrElements=_mkEmptyElementList(numRows);
    if (ptrElements) {
      _mergeObjectsIntoElementsByRowY(object, ptrElements, numColumns);
      AQDG_Placement_LayoutAndStretch(ptrElements, numRows,
                                      AQDG_Object_GetHeight(object),
                                      AQDG_Object_GetBorderTop(object),
                                      AQDG_Object_GetBorderBottom(object),
                                      AQDG_Object_GetVSpacing(object));
      _setChildrenFromElementsByRowY(object, ptrElements, numColumns);
      free(ptrElements);

    }
  }
  AQDG_LayoutObject_LayoutChildren(object);
  return 0;
}



int _layoutByColumns(AQDG_OBJECT *object)
{
#warning "TODO"
#if 0
  AQDG_OBJECT_MLAYOUT *xo;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT, object);
  if (xo) {
    AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements;
    int numElements;

    numElements=xo->numRowsOrColumns;
    ptrElements=_mkEmptyElementList(numElements);
    if (ptrElements) {
      _mergeObjectsIntoElementsByColumnY(object, ptrElements, numElements);
      AQDG_Placement_LayoutAndStretch(ptrElements, numElements,
				      AQDG_Object_GetHeight(object),
				      AQDG_Object_GetBorderTop(object),
				      AQDG_Object_GetBorderBottom(object),
				      AQDG_Object_GetVSpacing(object));
      _setChildrenFromElementsY(object, ptrElements, numElements);
      free(ptrElements);

      _layoutSecondaryX(object, numElements);
    }
  }
  AQDG_LayoutObject_LayoutChildren(object);
#endif
  return 0;
}



void _layoutSecondaryY(AQDG_OBJECT *object, int numElements)
{
  AQDG_OBJECT *child;

  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements;
    int i;
    // TODO: only distribute across row, not full widget!!
    /* handle numElements */
    ptrElements=AQDG_LayoutObject_ObjectList2ElementsY(child, numElements);
    AQDG_Placement_LayoutSecondaryAxis(ptrElements, numElements,
				       AQDG_Object_GetHeight(object),
				       AQDG_Object_GetBorderTop(object),
				       AQDG_Object_GetBorderBottom(object));
    AQDG_LayoutObject_Elements2ObjectListY(child, ptrElements, numElements);
    free(ptrElements);

    /* skip numElements */
    i=0;
    while(child && i<numElements) {
      child=AQDG_Object_Tree2_GetNext(child);
      i++;
    }
  }
}



void _layoutSecondaryX(AQDG_OBJECT *object, int numElements)
{
  AQDG_OBJECT *child;

  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements;
    int i;

    ptrElements=AQDG_LayoutObject_ObjectList2ElementsX(child, numElements);
    AQDG_Placement_LayoutSecondaryAxis(ptrElements, numElements,
				       AQDG_Object_GetWidth(object),
				       AQDG_Object_GetBorderLeft(object),
				       AQDG_Object_GetBorderRight(object));
    AQDG_LayoutObject_Elements2ObjectListX(child, ptrElements, numElements);
    free(ptrElements);

    i=0;
    while(child && i<numElements) {
      child=AQDG_Object_Tree2_GetNext(child);
      i++;
    }
  }
}



AQDG_PLACEMENT_LAYOUT_ELEMENT *_mkEmptyElementList(int num)
{
  if (num) {
    AQDG_PLACEMENT_LAYOUT_ELEMENT *elements;

    elements=(AQDG_PLACEMENT_LAYOUT_ELEMENT*) malloc(num*sizeof(AQDG_PLACEMENT_LAYOUT_ELEMENT));
    if (elements) {
      memset(elements, 0, num*sizeof(AQDG_PLACEMENT_LAYOUT_ELEMENT));
      return elements;
    }
  }

  return NULL;
}



void _mergeObjectsIntoElementsByRowX(AQDG_OBJECT *object, AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements, int numElements)
{
  AQDG_OBJECT *child;
  int i=0;

  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    _mergeObjectXIntoElement(&ptrElements[i%numElements], child);
    i++;
    child=AQDG_Object_Tree2_GetNext(child);
  }
}



void _mergeObjectsIntoElementsByRowY(AQDG_OBJECT *object, AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements, int numElements)
{
  AQDG_OBJECT *child;
  int r=0;
  int i=0;

  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    _mergeObjectYIntoElement(&ptrElements[r], child);
    i++;
    if (i>=numElements) {
      r++;
      i=0;
    }
    child=AQDG_Object_Tree2_GetNext(child);
  }
}



void _mergeObjectsIntoColumnElementsY(AQDG_OBJECT *object, AQDG_PLACEMENT_LAYOUT_ELEMENT *ptrElements, int numElements)
{
  AQDG_OBJECT *child;
  int i=0;

  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    _mergeObjectYIntoElement(&ptrElements[i%numElements], child);
    i++;
    child=AQDG_Object_Tree2_GetNext(child);
  }
}



void _mergeObjectXIntoElement(AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr, AQDG_OBJECT *object)
{
  uint32_t opts;
  int len;

  opts=AQDG_Object_GetOptions(object);
  eptr->pos=0;
  len=AQDG_Object_GetDefaultWidth(object);
  eptr->length=(len>eptr->length)?len:(eptr->length);
  eptr->opts=(opts & AQDG_OBJECT_OPTIONS_STRETCHX)?AQDG_PLACEMENT_LAYOUT_ELEMENT_OPTS_STRETCH:0;
  eptr->opts|=(opts  & AQDG_OBJECT_OPTIONS_HALIGNCENTER)?AQDG_PLACEMENT_LAYOUT_ELEMENT_OPTS_CENTER:0;
  eptr->opts|=(opts  & AQDG_OBJECT_OPTIONS_HALIGNRIGHT)?AQDG_PLACEMENT_LAYOUT_ELEMENT_OPTS_RIGHT:0;
}



void _mergeObjectYIntoElement(AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr, AQDG_OBJECT *object)
{
  uint32_t opts;
  int len;

  opts=AQDG_Object_GetOptions(object);
  eptr->pos=0;
  len=AQDG_Object_GetDefaultHeight(object);
  eptr->length=(len>eptr->length)?len:(eptr->length);
  eptr->opts=(opts & AQDG_OBJECT_OPTIONS_STRETCHY)?AQDG_PLACEMENT_LAYOUT_ELEMENT_OPTS_STRETCH:0;
  eptr->opts|=(opts  & AQDG_OBJECT_OPTIONS_VALIGNCENTER)?AQDG_PLACEMENT_LAYOUT_ELEMENT_OPTS_CENTER:0;
  eptr->opts|=(opts  & AQDG_OBJECT_OPTIONS_VALIGNBOTTOM)?AQDG_PLACEMENT_LAYOUT_ELEMENT_OPTS_RIGHT:0;
}



void _setChildrenFromElementsByRowX(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *elements, int num)
{
  if (num && elements) {
    AQDG_OBJECT *child;
    int i=0;

    child=AQDG_Object_Tree2_GetFirstChild(object);
    while(child) {
      _setObjectFromElementX(child, &elements[i%num]);
      i++;
      child=AQDG_Object_Tree2_GetNext(child);
    }
  }
}



void _setChildrenFromElementsByRowY(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *elements, int num)
{
  if (num && elements) {
    AQDG_OBJECT *child;
    int i=0;
    int r=0;

    child=AQDG_Object_Tree2_GetFirstChild(object);
    while(child) {
      _setObjectFromElementY(child, &elements[r]);
      i++;
      if (i>=num) {
        r++;
        i=0;
      }
      child=AQDG_Object_Tree2_GetNext(child);
    }
  }
}



void _setChildrenFromElementsY(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *elements, int num)
{
  if (num && elements) {
    AQDG_OBJECT *child;
    int i=0;

    child=AQDG_Object_Tree2_GetFirstChild(object);
    while(child) {
      _setObjectFromElementY(child, &elements[i%num]);
      i++;
      child=AQDG_Object_Tree2_GetNext(child);
    }
  }
}



void _setObjectFromElementX(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr)
{
  AQDG_Object_SetRelativeX(object, eptr->pos);
  AQDG_Object_SetWidth(object, eptr->length);
}



void _setObjectFromElementY(AQDG_OBJECT *object, const AQDG_PLACEMENT_LAYOUT_ELEMENT *eptr)
{
  AQDG_Object_SetRelativeY(object, eptr->pos);
  AQDG_Object_SetHeight(object, eptr->length);
}



int _getDefaultWidthByRows(AQDG_OBJECT *object)
{
  AQDG_OBJECT_MLAYOUT *xo;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT, object);
  return xo?_calcWidthByRows(object, xo->numRowsOrColumns):0;
}



int _getDefaultHeightByRows(AQDG_OBJECT *object)
{
  AQDG_OBJECT_MLAYOUT *xo;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT, object);
  return xo?_calcHeightByRows(object, xo->numRowsOrColumns):0;
}



int _getDefaultWidthByColumns(AQDG_OBJECT *object)
{
  AQDG_OBJECT_MLAYOUT *xo;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT, object);
  return xo?_calcWidthByColumns(object, xo->numRowsOrColumns):0;
}



int _getDefaultHeightByColumns(AQDG_OBJECT *object)
{
  AQDG_OBJECT_MLAYOUT *xo;

  xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_OBJECT_MLAYOUT, object);
  return xo?_calcHeightByColumns(object, xo->numRowsOrColumns):0;
}



int _calcWidthByRows(AQDG_OBJECT *object, int columns)
{
  AQDG_OBJECT *child;
  int i=0;
  int w;

  w=AQDG_Object_GetBorderLeft(object);
  i=0;
  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child && i<columns) {
    int cw;

    cw=_calcMaxColumnWidth(child, columns);
    w+=cw;
    i++;
    child=AQDG_Object_Tree2_GetNext(child);
    if (child)
      w+=AQDG_Object_GetHSpacing(object);
  }
  w+=AQDG_Object_GetBorderRight(object);

  return w;
}



int _calcMaxColumnWidth(AQDG_OBJECT *object, int columns)
{
  int row=0;
  int w=0;

  while(object) {
    int cw;
    int i=0;

    cw=AQDG_Object_GetDefaultWidth(object);
    w=(cw>w)?cw:w;
    //DBG_ERROR(NULL, "row %d: %d (%s)", row, cw, AQDG_Object_GetName(object));

    while(object && i<columns) { /* skip columns */
      i++;
      object=AQDG_Object_Tree2_GetNext(object);
    }
    row++;
  }

  return w;
}





int _calcHeightByRows(const AQDG_OBJECT *object, int columns)
{
  AQDG_OBJECT *child;
  int i=0;
  int ch=0;
  int h;

  h=AQDG_Object_GetBorderTop(object);
  i=0;
  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    int k;

    k=AQDG_Object_GetDefaultHeight(child);
    ch=(k>ch)?k:ch;
    child=AQDG_Object_Tree2_GetNext(child);
    i++;
    if (i>=columns) {
      h+=ch;
      i=0;
      ch=0;
      if (child)
        h+=AQDG_Object_GetVSpacing(object);
    }
  }
  h+=AQDG_Object_GetBorderBottom(object);
  return h;
}



int _calcWidthByColumns(const AQDG_OBJECT *object, int rows)
{
  AQDG_OBJECT *child;
  int i=0;
  int cw=0;
  int w;

  w=AQDG_Object_GetBorderLeft(object);
  i=0;
  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child) {
    int k;

    k=AQDG_Object_GetDefaultWidth(child);
    cw=(k>cw)?k:cw;
    child=AQDG_Object_Tree2_GetNext(child);
    i++;
    if (i>=rows) {
      w+=cw;
      i=0;
      cw=0;
      if (child)
        w+=AQDG_Object_GetHSpacing(object);
    }
  }
  w+=AQDG_Object_GetBorderRight(object);
  return w;
}



int _calcHeightByColumns(const AQDG_OBJECT *object, int rows)
{
  AQDG_OBJECT *child;
  int i=0;
  int ch=0;
  int h;

  h=AQDG_Object_GetBorderTop(object);
  i=0;
  ch=0;
  child=AQDG_Object_Tree2_GetFirstChild(object);
  while(child && i<rows) {
    AQDG_OBJECT *n;

    /* get highest height of row */
    n=child;
    while(n) {
      int k;
      int j=0;

      k=AQDG_Object_GetDefaultHeight(n);
      ch=(k>ch)?k:ch;
      while(n && j<rows) { /* skip rows */
        j++;
        n=AQDG_Object_Tree2_GetNext(n);
      }
    }
    h+=ch;
    child=AQDG_Object_Tree2_GetNext(child);
    if (child)
      h+=AQDG_Object_GetVSpacing(object);
    i++;
  }
  h+=AQDG_Object_GetBorderBottom(object);
  return h;
}





