/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 2001-3 Paul Murrell
 *                2003-2013 The R Core Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, a copy is available at
 *  https://www.R-project.org/Licenses/
 */

#include "grid.h"

/* This stuff always returns an LViewportLocation in "npc" units
 */

int layoutNRow(SEXP l) {
    return INTEGER(VECTOR_ELT(l, LAYOUT_NROW))[0];
}

int layoutNCol(SEXP l) {
    return INTEGER(VECTOR_ELT(l, LAYOUT_NCOL))[0];
}

SEXP layoutWidths(SEXP l) {
    return VECTOR_ELT(l, LAYOUT_WIDTHS);
}

SEXP layoutHeights(SEXP l) {
    return VECTOR_ELT(l, LAYOUT_HEIGHTS);
}

int layoutRespect(SEXP l) {
    return INTEGER(VECTOR_ELT(l, LAYOUT_VRESPECT))[0];
}

int* layoutRespectMat(SEXP l) {
    return INTEGER(VECTOR_ELT(l, LAYOUT_MRESPECT));
}

double layoutHJust(SEXP l) {
    return REAL(VECTOR_ELT(l, LAYOUT_VJUST))[0];
}

double layoutVJust(SEXP l) {
    return REAL(VECTOR_ELT(l, LAYOUT_VJUST))[1];
}

Rboolean relativeUnit(SEXP unit, int index,
		   pGEDevDesc dd) {
    return pureNullUnit(unit, index, dd);
}

void findRelWidths(SEXP layout, int *relativeWidths,
		   pGEDevDesc dd) 
{
    int i;
    SEXP widths = layoutWidths(layout);
    for (i=0; i<layoutNCol(layout); i++) 
	relativeWidths[i] = relativeUnit(widths, i, dd);
}

void findRelHeights(SEXP layout, int *relativeHeights,
		   pGEDevDesc dd) 
{
    int i;
    SEXP heights = layoutHeights(layout);
    for (i=0; i<layoutNRow(layout); i++) 
	relativeHeights[i] = relativeUnit(heights, i, dd);
}

void allocateKnownWidths(SEXP layout, 
			 int *relativeWidths, 
			 double parentWidthCM, double parentHeightCM,
			 LViewportContext parentContext,
			 const pGEcontext parentgc,
			 pGEDevDesc dd,
			 double *npcWidths, double *widthLeftCM) 
{
    int i;
    SEXP widths = layoutWidths(layout);
    for (i=0; i<layoutNCol(layout); i++) 
	if (!relativeWidths[i]) {
	    npcWidths[i] = transformWidth(widths, i, parentContext,
					  parentgc,
					  parentWidthCM, parentHeightCM, 
					  0, 0, dd)*2.54;
	    *widthLeftCM -= npcWidths[i];
	}
}

void allocateKnownHeights(SEXP layout, 
			  int *relativeHeights,
			  double parentWidthCM, double parentHeightCM,
			  LViewportContext parentContext,
			  const pGEcontext parentgc,
			  pGEDevDesc dd,
			  double *npcHeights, double *heightLeftCM) 
{
    int i;
    SEXP heights = layoutHeights(layout);
    for (i=0; i<layoutNRow(layout); i++) 
	if (!relativeHeights[i]) {
	    npcHeights[i] = transformHeight(heights, i, parentContext,
					    parentgc,
					    parentWidthCM, parentHeightCM, 
					    0, 0, dd)*2.54;
	    *heightLeftCM -= npcHeights[i];
	}
}

int colRespected(int col, SEXP layout) {
    int i;
    int result = 0;
    int respect = layoutRespect(layout);
    int *respectMat = layoutRespectMat(layout);
    if (respect == 1)
	result = 1;
    else
	for (i=0; i<layoutNRow(layout); i++)
	    if (respectMat[col*layoutNRow(layout) + i] != 0)
		result = 1;
    return result;
}

int rowRespected(int row, SEXP layout) {
    int i;
    int result = 0;
    int respect = layoutRespect(layout);
    int *respectMat = layoutRespectMat(layout);
    if (respect == 1)
	result = 1;
    else
	for (i=0; i<layoutNCol(layout); i++)
	    if (respectMat[i*layoutNRow(layout) + row] != 0)
		result = 1;
    return result;
}

/* 
 * These sum up ALL relative widths and heights (unit = "null")
 * Some effort is made to find all truly null units
 * (e.g., including a grobwidth unit where the grob's width is null)
 */
double totalWidth(SEXP layout, int *relativeWidths,
		  LViewportContext parentContext,
		  const pGEcontext parentgc,
		  pGEDevDesc dd)
{
    int i;
    SEXP widths = layoutWidths(layout);
    double totalWidth = 0;
    for (i=0; i<layoutNCol(layout); i++) 
	if (relativeWidths[i])
	    totalWidth += transformWidth(widths, i, parentContext, 
					 parentgc,
					 /* 
					  * NOTE: 0, 0, here is ok
					  * because we are only 
					  * obtaining "null" units
					  */
					 0, 0, 1, 0, dd);
    return totalWidth;
}

double totalHeight(SEXP layout, int *relativeHeights,
		   LViewportContext parentContext,
		   const pGEcontext parentgc,
		   pGEDevDesc dd)
{
    int i;
    SEXP heights = layoutHeights(layout);
    double totalHeight = 0;
    for (i=0; i<layoutNRow(layout); i++) 
	if (relativeHeights[i])
	    totalHeight += transformHeight(heights, i, parentContext, 
					   parentgc,
					   /* 
					    * NOTE: 0, 0, here is ok
					    * because we are only 
					    * obtaining "null" units
					    */
					   0, 0, 1, 0, dd);
    return totalHeight;
}

void allocateRespected(SEXP layout, 
		       int *relativeWidths, int *relativeHeights,
		       double *reducedWidthCM, double *reducedHeightCM,
		       LViewportContext parentContext,
		       const pGEcontext parentgc,
		       pGEDevDesc dd,
		       double *npcWidths, double *npcHeights)
{
    int i;
    SEXP widths = layoutWidths(layout);
    SEXP heights = layoutHeights(layout);
    int respect = layoutRespect(layout);
    double sumWidth = totalWidth(layout, relativeWidths, parentContext,
				 parentgc, dd);
    double sumHeight = totalHeight(layout, relativeHeights, parentContext,
				   parentgc, dd);
    double denom, mult;
    double tempWidthCM = *reducedWidthCM;
    double tempHeightCM = *reducedHeightCM;
    if (respect > 0) {
	/* Determine whether aspect ratio of available space is
	 * bigger or smaller than aspect ratio of layout
	 */
	// NB: widths could be zero
	// if ((tempHeightCM / tempWidthCM) > (sumHeight / sumWidth)) {
	if ( tempHeightCM * sumWidth > sumHeight * tempWidthCM) {
	    denom = sumWidth;
	    mult = tempWidthCM;
	}
	else {
	    denom = sumHeight;
	    mult = tempHeightCM;
	}
	/* Allocate respected widths 
	 */
	for (i=0; i<layoutNCol(layout); i++)
	    if (relativeWidths[i])
		if (colRespected(i, layout)) {
		    /* 
		     * Special case of respect, but sumHeight = 0.
		     * Action is to allocate widths as if unrespected.
		     * Ok to test == 0 because will only be 0 if 
		     * all relative heights are actually exactly 0.
		     */
		    if (sumHeight == 0) {
			denom = sumWidth;
			mult = tempWidthCM;
		    }
		    /* Build a unit SEXP with a single value and no data
		     */
		    npcWidths[i] = pureNullUnitValue(widths, i) / 
                        denom*mult;
		    *reducedWidthCM -= npcWidths[i];
		}
	/* Allocate respected heights
	 */
	for (i=0; i<layoutNRow(layout); i++)
	    if (relativeHeights[i])
		if (rowRespected(i, layout)) {
		    /* 
		     * Special case of respect, but sumWidth = 0.
		     * Action is to allocate widths as if unrespected.
		     * Ok to test == 0 because will only be 0 if 
		     * all relative heights are actually exactly 0.
		     */
		    if (sumWidth == 0) {
			denom = sumHeight;
			mult = tempHeightCM;
		    }
		    npcHeights[i] = pureNullUnitValue(heights, i) / 
                        denom*mult;
		    *reducedHeightCM -= npcHeights[i];
		}
    }
}

void setRespectedZero(SEXP layout, 
                      int *relativeWidths, int *relativeHeights,
                      double *npcWidths, double *npcHeights)
{
    int i;
    for (i=0; i<layoutNCol(layout); i++)
        if (relativeWidths[i])
            if (colRespected(i, layout)) 
                npcWidths[i] = 0;
    for (i=0; i<layoutNRow(layout); i++)
        if (relativeHeights[i])
            if (rowRespected(i, layout)) 
                npcHeights[i] = 0;
}

/* These sum up unrespected relative widths and heights (unit = "null")
 */
double totalUnrespectedWidth(SEXP layout, int *relativeWidths,
			     LViewportContext parentContext,
			     const pGEcontext parentgc,
			     pGEDevDesc dd)
{
    int i;
    SEXP widths = layoutWidths(layout);
    double totalWidth = 0;
    for (i=0; i<layoutNCol(layout); i++) 
	if (relativeWidths[i])
	    if (!colRespected(i, layout))
		totalWidth += transformWidth(widths, i, parentContext, 
					     parentgc,
					     /* 
					      * NOTE: 0, 0, here is ok
					      * because we are only 
					      * obtaining "null" units
					      */
					     0, 0, 1, 0, dd);
    return totalWidth;
}

double totalUnrespectedHeight(SEXP layout, int *relativeHeights,
			      LViewportContext parentContext,
			      const pGEcontext parentgc,
			      pGEDevDesc dd)
{
    int i;
    SEXP heights = layoutHeights(layout);
    double totalHeight = 0;
    for (i=0; i<layoutNRow(layout); i++) 
	if (relativeHeights[i])
	    if (!rowRespected(i, layout))
		totalHeight += transformHeight(heights, i, parentContext, 
					       parentgc,
					       /* 
						* NOTE: 0, 0, here is ok
						* because we are only 
						* obtaining "null" units
						*/
					       0, 0, 1, 0, dd);
    return totalHeight;
}


void setRemainingWidthZero(SEXP layout, 
                           int *relativeWidths, 
                           double *npcWidths)
{
    int i;
    for (i=0; i<layoutNCol(layout); i++)
        if (relativeWidths[i])
            if (!colRespected(i, layout)) 
                npcWidths[i] = 0;
}

void allocateRemainingWidth(SEXP layout, int *relativeWidths,
			    double remainingWidthCM, 
			    LViewportContext parentContext, 
			    const pGEcontext parentgc,
			    pGEDevDesc dd,
			    double *npcWidths)
{
    int i;
    SEXP widths = layoutWidths(layout);
    double sumWidth;
    sumWidth = totalUnrespectedWidth(layout, relativeWidths,
				     parentContext, parentgc, dd);
    if (sumWidth > 0) {
        for (i=0; i<layoutNCol(layout); i++) 
            if (relativeWidths[i])
                if (!colRespected(i, layout))
                    npcWidths[i] = remainingWidthCM*
                        transformWidth(widths, i, parentContext, parentgc,
				   /* 
				    * NOTE: 0, 0, here is ok
				    * because we are only 
				    * obtaining "null" units
				    */
                                       0, 0, 1, 0, dd)/
                        sumWidth;
    } else {
        /* 
         * If ALL relative widths are zero then they all get 
         * allocated zero width
         */
        setRemainingWidthZero(layout, relativeWidths, npcWidths);
    }
}

void setRemainingHeightZero(SEXP layout, 
                            int *relativeHeights, 
                            double *npcHeights)
{
    int i;
    for (i=0; i<layoutNRow(layout); i++)
        if (relativeHeights[i])
            if (!rowRespected(i, layout)) 
                npcHeights[i] = 0;
}

void allocateRemainingHeight(SEXP layout, int *relativeHeights,
			     double remainingHeightCM, 
			     LViewportContext parentContext,
			     const pGEcontext parentgc,
			     pGEDevDesc dd,
			     double *npcHeights)
{
    int i;
    SEXP heights = layoutHeights(layout);
    double sumHeight;
    sumHeight = totalUnrespectedHeight(layout, relativeHeights,
				       parentContext, parentgc, dd);
    if (sumHeight > 0) {
        for (i=0; i<layoutNRow(layout); i++) 
            if (relativeHeights[i])
                if (!rowRespected(i, layout))
                    npcHeights[i] = remainingHeightCM*
                        transformHeight(heights, i, parentContext, parentgc,
				    /* 
				     * NOTE: 0, 0, here is ok
				     * because we are only 
				     * obtaining "null" units
				     */
                                        0, 0, 1, 0, dd)/
                        sumHeight;
    } else {
        /* 
         * If ALL relative heights are zero then they all get 
         * allocated zero height
         */
        setRemainingHeightZero(layout, relativeHeights, npcHeights);
    }
}

static double sumDims(double dims[], int from, int to)
{
    int i;
    double s = 0;
    for (i = from; i < to + 1; i++)
	s = s + dims[i];
    return s;
}

static void subRegion(SEXP layout,
		      int minrow, int maxrow, int mincol, int maxcol,
		      double widths[], double heights[], 
                      double parentWidthCM, double parentHeightCM,
		      double *left, double *bottom, 
		      double *width, double *height) 
{
    double hjust = layoutHJust(layout);
    double vjust = layoutVJust(layout);
    double totalWidth = sumDims(widths, 0, layoutNCol(layout) - 1);
    double totalHeight = sumDims(heights, 0, layoutNRow(layout) - 1);
    *width = sumDims(widths, mincol, maxcol);
    *height = sumDims(heights, minrow, maxrow);    
    /* widths and heights are in CM */
    *left = parentWidthCM*hjust - totalWidth*hjust + 
        sumDims(widths, 0, mincol - 1);
    *bottom =  parentHeightCM*vjust + (1 - vjust)*totalHeight - 
	sumDims(heights, 0, maxrow);
    /*
     * From when hjust and vjust were enums
     *
    switch (layoutHJust(layout)) {
    case L_LEFT:
	*left = sumDims(widths, 0, mincol - 1);
	break;
    case L_RIGHT: 
	*left = 1 - sumDims(widths, mincol, layoutNCol(layout) - 1);
	break;
    case L_CENTRE: 
    case L_CENTER:
	*left = (0.5 - totalWidth/2) + sumDims(widths, 0, mincol - 1);
	break;
    }
    switch (layoutVJust(layout)) {
    case L_BOTTOM:
	*bottom = totalHeight - sumDims(heights, 0, maxrow);
	break;
    case L_TOP:
	*bottom = 1 - sumDims(heights, 0, maxrow);
	break;
    case L_CENTRE: 
    case L_CENTER:
	*bottom = (0.5 - totalHeight/2) + totalHeight
	    - sumDims(heights, 0, maxrow);
    }
    */
}

Rboolean allocationRemaining(double initial, double remaining)
{
    if (initial == 0) {
        return TRUE;
    } else if (initial > 0) {
        return remaining > 0;
    } else {
        return remaining < 0;
    }
}

void calcViewportLayout(SEXP viewport,
			double parentWidthCM,
			double parentHeightCM,
			LViewportContext parentContext,
			const pGEcontext parentgc,
			pGEDevDesc dd)
{
    int i;
    SEXP currentWidths, currentHeights;
    SEXP layout = viewportLayout(viewport);
    double *npcWidths = (double *) R_alloc(layoutNCol(layout), sizeof(double));
    double *npcHeights = (double *) R_alloc(layoutNRow(layout), 
					    sizeof(double));
    int *relativeWidths = (int *) R_alloc(layoutNCol(layout), sizeof(int));
    int *relativeHeights = (int *) R_alloc(layoutNRow(layout), sizeof(int));    
    double reducedWidthCM = parentWidthCM;
    double reducedHeightCM = parentHeightCM;
    /* Figure out which rows and cols have relative heights and widths 
     */
    findRelWidths(layout, relativeWidths, dd);
    findRelHeights(layout, relativeHeights, dd);
    /* For any width or height which has a unit other than "null"
     * we can immediately figure out its physical size.
     * We do this and return the widthCM and heightCM 
     * remaining after these widths and heights have been allocated
     */
    allocateKnownWidths(layout, relativeWidths,
			parentWidthCM, parentHeightCM, 
			parentContext, parentgc,
			dd, npcWidths,
			&reducedWidthCM);
    allocateKnownHeights(layout, relativeHeights,
			 parentWidthCM, parentHeightCM, 
			 parentContext, parentgc,
			 dd, npcHeights, 
			 &reducedHeightCM);

    /* Now allocate respected widths and heights and return
     * widthCM and heightCM remaining 
     */
    if (allocationRemaining(parentWidthCM, reducedWidthCM) ||
        allocationRemaining(parentHeightCM, reducedHeightCM)) {
        allocateRespected(layout, relativeWidths, relativeHeights, 
                          &reducedWidthCM, &reducedHeightCM,
                          parentContext, parentgc, dd,
                          npcWidths, npcHeights);
    } else {
        /* 
         * IF EITHER we started with ZERO widthCM and heightCM
         *    OR we've used up all the widthCM and heightCM
         * THEN all respected widths/heights get ZERO
         */ 
        setRespectedZero(layout, relativeWidths, relativeHeights, 
                         npcWidths, npcHeights);
    }
    /* Now allocate relative widths and heights (unit = "null")
     * in the remaining space
     */
    if (allocationRemaining(parentWidthCM, reducedWidthCM)) {
        allocateRemainingWidth(layout, relativeWidths,
                               reducedWidthCM, 
                               parentContext, parentgc, dd, npcWidths);
    } else {
        /* 
         * IF EITHER we started with ZERO width
         *    OR we've used up all the width
         * THEN any relative widths get ZERO
         */ 
        setRemainingWidthZero(layout, relativeWidths, npcWidths);
    }
    if (allocationRemaining(parentHeightCM, reducedHeightCM)) {
        allocateRemainingHeight(layout, relativeHeights,
                                reducedHeightCM, 
                                parentContext, parentgc, dd, npcHeights);
    } else {
        /* 
         * IF EITHER we started with ZERO height
         *    OR we've used up all the height
         * THEN any relative heights get ZERO
         */ 
        setRemainingHeightZero(layout, relativeHeights, npcHeights);
    }
    /* Record the widths and heights in the viewport
     */
    PROTECT(currentWidths = allocVector(REALSXP, layoutNCol(layout)));
    PROTECT(currentHeights = allocVector(REALSXP, layoutNRow(layout)));
    for (i=0; i<layoutNCol(layout); i++) {
        /* Layout widths are stored in CM
         */
	REAL(currentWidths)[i] = npcWidths[i];
    }
    for (i=0; i<layoutNRow(layout); i++) {
        /* Layout heights are stored in CM
         */
	REAL(currentHeights)[i] = npcHeights[i];
    }
    SET_VECTOR_ELT(viewport, PVP_WIDTHS, currentWidths);
    SET_VECTOR_ELT(viewport, PVP_HEIGHTS, currentHeights);
    UNPROTECT(2);
}

Rboolean checkPosRowPosCol(SEXP vp, SEXP parent) 
{
    int ncol = layoutNCol(viewportLayout(parent));
    int nrow = layoutNRow(viewportLayout(parent));
    if (!isNull(viewportLayoutPosRow(vp)) &&
	(INTEGER(viewportLayoutPosRow(vp))[0] < 1 ||
	 INTEGER(viewportLayoutPosRow(vp))[1] > nrow))
        error(_("invalid 'layout.pos.row'"));
    if (!isNull(viewportLayoutPosCol(vp)) &&
	(INTEGER(viewportLayoutPosCol(vp))[0] < 1 ||
	 INTEGER(viewportLayoutPosCol(vp))[1] > ncol))
        error(_("invalid 'layout.pos.col'"));
    return TRUE;
}

void calcViewportLocationFromLayout(SEXP layoutPosRow,
				    SEXP layoutPosCol,
				    SEXP parent,
				    LViewportLocation *vpl)
{
    int minrow, maxrow, mincol, maxcol;
    double x, y, width, height;
    SEXP vpx, vpy, vpwidth, vpheight;
    SEXP layout = viewportLayout(parent);
    /* It is possible for ONE of layoutPosRow and layoutPosCol to
     * be NULL;  this is interpreted as "occupy all rows/cols"
     * NOTE: The " - 1" is there because R is 1-based and C is zero-based 
     */
    if (isNull(layoutPosRow)) {
	minrow = 0;
	maxrow = layoutNRow(layout) - 1;
    } else {
	minrow = INTEGER(layoutPosRow)[0] - 1;
	maxrow = INTEGER(layoutPosRow)[1] - 1;
    }
    if (isNull(layoutPosCol)) {
	mincol = 0;
	maxcol = layoutNCol(layout) - 1;
    } else {
	mincol = INTEGER(layoutPosCol)[0] - 1;
	maxcol = INTEGER(layoutPosCol)[1] - 1;
    }
    /* Put the relevant values into vpl */
    subRegion(viewportLayout(parent), minrow, maxrow, mincol, maxcol,
	      REAL(viewportLayoutWidths(parent)), 
	      REAL(viewportLayoutHeights(parent)),
              REAL(viewportWidthCM(parent))[0],
              REAL(viewportHeightCM(parent))[0],
	      &x, &y, &width, &height);
    /* Layout widths and heights are stored in CM
     */
    PROTECT(vpx = unit(x, L_CM));
    vpl->x = vpx;
    PROTECT(vpy = unit(y, L_CM));
    vpl->y = vpy;
    PROTECT(vpwidth = unit(width, L_CM));
    vpl->width = vpwidth;
    PROTECT(vpheight = unit(height, L_CM));
    vpl->height = vpheight;
    vpl->hjust = 0;
    vpl->vjust = 0;
    /* Question:  Is there any chance that these newly-allocated 
     * unit SEXPs will get corrupted after this unprotect ??
     */
    UNPROTECT(4);
}

