/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 2001-3 Paul Murrell
 *                2003-2014 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"
#include <string.h>

/* Some access methods for gpars */
SEXP gpFontSizeSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_FONTSIZE);
}
double gpFontSize(SEXP gp, int i) {
    SEXP fontsize = gpFontSizeSXP(gp);
    return REAL(fontsize)[i % LENGTH(fontsize)];
}
double gpFontSize2(SEXP gp, int i, int* gpIsScalar) {
    SEXP fontsize = gpFontSizeSXP(gp);
    gpIsScalar[GP_FONTSIZE] = LENGTH(fontsize) == 1;
    return REAL(fontsize)[i % LENGTH(fontsize)];
}

SEXP gpLineHeightSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_LINEHEIGHT);
}

double gpLineHeight(SEXP gp, int i) {
    SEXP lineheight = gpLineHeightSXP(gp);
    return REAL(lineheight)[i % LENGTH(lineheight)];
}
double gpLineHeight2(SEXP gp, int i, int* gpIsScalar) {
    SEXP lineheight = gpLineHeightSXP(gp);
    gpIsScalar[GP_LINEHEIGHT] = LENGTH(lineheight) == 1;
    return REAL(lineheight)[i % LENGTH(lineheight)];
}

SEXP gpColSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_COL);
}
/* grid has no concept of 'colour 0' (bg in base) */
int gpCol(SEXP gp, int i) {
    SEXP col = gpColSXP(gp);
    int result;
    if (isNull(col))
	result = R_TRANWHITE;
    else
	result = RGBpar3(col, i % LENGTH(col), R_TRANWHITE);
    return result;
}
int gpCol2(SEXP gp, int i, int* gpIsScalar) {
    SEXP col = gpColSXP(gp);
    gpIsScalar[GP_COL] = LENGTH(col) == 1;
    int result;
    if (isNull(col))
        result = R_TRANWHITE;
    else
        result = RGBpar3(col, i % LENGTH(col), R_TRANWHITE);
    return result;
}

SEXP gpFillSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_FILL);
}

int gpFill(SEXP gp, int i) {
    SEXP fill = gpFillSXP(gp);
    int result;
    if (isNull(fill))
	result = R_TRANWHITE;
    else
	result = RGBpar3(fill, i % LENGTH(fill), R_TRANWHITE);
    return result;
}
int gpFill2(SEXP gp, int i, int* gpIsScalar) {
    SEXP fill = gpFillSXP(gp);
    gpIsScalar[GP_FILL] = LENGTH(fill) == 1;
    int result;
    if (isNull(fill))
        result = R_TRANWHITE;
    else
        result = RGBpar3(fill, i % LENGTH(fill), R_TRANWHITE);
    return result;
}

SEXP gpGammaSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_GAMMA);
}

double gpGamma(SEXP gp, int i) {
    SEXP gamma = gpGammaSXP(gp);
    return REAL(gamma)[i % LENGTH(gamma)];
}
double gpGamma2(SEXP gp, int i, int* gpIsScalar) {
    SEXP gamma = gpGammaSXP(gp);
    gpIsScalar[GP_GAMMA] = LENGTH(gamma) == 1;
    return REAL(gamma)[i % LENGTH(gamma)];
}

SEXP gpLineTypeSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_LTY);
}

int gpLineType(SEXP gp, int i) {
    SEXP linetype = gpLineTypeSXP(gp);
    return GE_LTYpar(linetype, i % LENGTH(linetype));
}
int gpLineType2(SEXP gp, int i, int* gpIsScalar) {
    SEXP linetype = gpLineTypeSXP(gp);
    gpIsScalar[GP_LTY] = LENGTH(linetype) == 1;
    return GE_LTYpar(linetype, i % LENGTH(linetype));
}

SEXP gpLineWidthSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_LWD);
}

double gpLineWidth(SEXP gp, int i) {
    SEXP linewidth = gpLineWidthSXP(gp);
    return REAL(linewidth)[i % LENGTH(linewidth)];
}
double gpLineWidth2(SEXP gp, int i, int* gpIsScalar) {
    SEXP linewidth = gpLineWidthSXP(gp);
    gpIsScalar[GP_LWD] = LENGTH(linewidth) == 1;
    return REAL(linewidth)[i % LENGTH(linewidth)];
}

SEXP gpCexSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_CEX);
}

double gpCex(SEXP gp, int i) {
    SEXP cex = gpCexSXP(gp);
    return REAL(cex)[i % LENGTH(cex)];
}
double gpCex2(SEXP gp, int i, int* gpIsScalar) {
    SEXP cex = gpCexSXP(gp);
    gpIsScalar[GP_CEX] = LENGTH(cex) == 1;
    return REAL(cex)[i % LENGTH(cex)];
}

SEXP gpFontSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_FONT);
}

int gpFont(SEXP gp, int i) {
    SEXP font = gpFontSXP(gp);
    return INTEGER(font)[i % LENGTH(font)];
}
int gpFont2(SEXP gp, int i, int* gpIsScalar) {
    SEXP font = gpFontSXP(gp);
    gpIsScalar[GP_FONT] = LENGTH(font) == 1;
    return INTEGER(font)[i % LENGTH(font)];
}

SEXP gpFontFamilySXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_FONTFAMILY);
}

const char* gpFontFamily(SEXP gp, int i) {
    SEXP fontfamily = gpFontFamilySXP(gp);
    return CHAR(STRING_ELT(fontfamily, i % LENGTH(fontfamily)));
}
const char* gpFontFamily2(SEXP gp, int i, int* gpIsScalar) {
    SEXP fontfamily = gpFontFamilySXP(gp);
    gpIsScalar[GP_FONTFAMILY] = LENGTH(fontfamily) == 1;
    return CHAR(STRING_ELT(fontfamily, i % LENGTH(fontfamily)));
}

SEXP gpAlphaSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_ALPHA);
}

double gpAlpha(SEXP gp, int i) {
    SEXP alpha = gpAlphaSXP(gp);
    return REAL(alpha)[i % LENGTH(alpha)];
}
double gpAlpha2(SEXP gp, int i, int* gpIsScalar) {
    SEXP alpha = gpAlphaSXP(gp);
    gpIsScalar[GP_ALPHA] = LENGTH(alpha) == 1;
    return REAL(alpha)[i % LENGTH(alpha)];
}

SEXP gpLineEndSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_LINEEND);
}

R_GE_lineend gpLineEnd(SEXP gp, int i) {
    SEXP lineend = gpLineEndSXP(gp);
    return GE_LENDpar(lineend, i % LENGTH(lineend));
}
R_GE_lineend gpLineEnd2(SEXP gp, int i, int* gpIsScalar) {
    SEXP lineend = gpLineEndSXP(gp);
    gpIsScalar[GP_LINEEND] = LENGTH(lineend) == 1;
    return GE_LENDpar(lineend, i % LENGTH(lineend));
}

SEXP gpLineJoinSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_LINEJOIN);
}

R_GE_linejoin gpLineJoin(SEXP gp, int i) {
    SEXP linejoin = gpLineJoinSXP(gp);
    return GE_LJOINpar(linejoin, i % LENGTH(linejoin));
}
R_GE_linejoin gpLineJoin2(SEXP gp, int i, int* gpIsScalar) {
    SEXP linejoin = gpLineJoinSXP(gp);
    gpIsScalar[GP_LINEJOIN] = LENGTH(linejoin) == 1;
    return GE_LJOINpar(linejoin, i % LENGTH(linejoin));
}

SEXP gpLineMitreSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_LINEMITRE);
}

double gpLineMitre(SEXP gp, int i) {
    SEXP linemitre = gpLineMitreSXP(gp);
    return REAL(linemitre)[i % LENGTH(linemitre)];
}
double gpLineMitre2(SEXP gp, int i, int* gpIsScalar) {
    SEXP linemitre = gpLineMitreSXP(gp);
    gpIsScalar[GP_LINEMITRE] = LENGTH(linemitre) == 1;
    return REAL(linemitre)[i % LENGTH(linemitre)];
}

SEXP gpLexSXP(SEXP gp) {
    return VECTOR_ELT(gp, GP_LEX);
}

double gpLex(SEXP gp, int i) {
    SEXP lex = gpLexSXP(gp);
    return REAL(lex)[i % LENGTH(lex)];
}
double gpLex2(SEXP gp, int i, int* gpIsScalar) {
    SEXP lex = gpLexSXP(gp);
    gpIsScalar[GP_LEX] = LENGTH(lex) == 1;
    return REAL(lex)[i % LENGTH(lex)];
}

/*
 * Never access fontface because fontface values are stored in font
 * Historical reasons ...
 */

/*
 * Combine gpar alpha with alpha level stored in colour 
 *
 * finalAlpha = gpAlpha*(R_ALPHA(col)/255)
 *
 * Based on my reading of how group alpha and individual
 * object alphas are combined in the SVG 1.0 docs
 *
 * Also has nice properties:
 *  (i)   range of finalAlpha is 0 to 1.
 *  (ii)  if either of gpAlpha or R_ALPHA(col) are 0 then finalAlpha = 0
 *        (i.e., can never make fully transparent colour less transparent).
 *  (iii) in order to get finalAlpha = 1, both gpAlpha and R_ALPHA(col)
 *        must be 1 (i.e., only way to get fully opaque is if both
 *        alpha levels are fully opaque).
 */
static unsigned int combineAlpha(double alpha, int col) 
{
    unsigned int newAlpha = (unsigned int)((alpha*(R_ALPHA(col)/255.0))*255);
    return R_RGBA(R_RED(col), R_GREEN(col), R_BLUE(col), newAlpha);
}

/* 
 * Generate an R_GE_gcontext from a gpar
 */
void gcontextFromgpar(SEXP gp, int i, const pGEcontext gc, pGEDevDesc dd) 
{
    /* 
     * Combine gpAlpha with col and fill
     */
    gc->col = combineAlpha(gpAlpha(gp, i), gpCol(gp, i));
    gc->fill = combineAlpha(gpAlpha(gp, i), gpFill(gp, i));
    gc->gamma = gpGamma(gp, i);
    /*
     * Combine gpLex with lwd
     * Also scale by GSS_SCALE (a "zoom" factor)
     */
    gc->lwd = gpLineWidth(gp, i) * gpLex(gp, i) * 
	REAL(gridStateElement(dd, GSS_SCALE))[0];
    gc->lty = gpLineType(gp, i);
    gc->lend = gpLineEnd(gp, i);
    gc->ljoin = gpLineJoin(gp, i);
    gc->lmitre = gpLineMitre(gp, i);
    gc->cex = gpCex(gp, i);
    /*
     * Scale by GSS_SCALE (a "zoom" factor)
     */
    gc->ps = gpFontSize(gp, i) * REAL(gridStateElement(dd, GSS_SCALE))[0];
    gc->lineheight = gpLineHeight(gp, i);
    gc->fontface = gpFont(gp, i);
    strcpy(gc->fontfamily, gpFontFamily(gp, i));
}

SEXP L_setGPar(SEXP gpars) 
{
    /* Set the value of the current gpars on the current device
     * Need to do this in here so that redrawing via R BASE display
     * list works 
     */
    /* Get the current device 
     */
    pGEDevDesc dd = getDevice();
    setGridStateElement(dd, GSS_GPAR, gpars);
    return R_NilValue;
}

SEXP L_getGPar(void) 
{
    /* Get the value of the current gpars on the current device
     * Need to do this in here so that redrawing via R BASE display
     * list works 
     */
    /* Get the current device 
     */
    pGEDevDesc dd = getDevice();
    return gridStateElement(dd, GSS_GPAR);
}

SEXP L_getGPsaved() 
{
    /* Get the current device 
     */
    pGEDevDesc dd = getDevice();
    return gridStateElement(dd, GSS_GPSAVED);
}

SEXP L_setGPsaved(SEXP gpars) 
{
    /* Get the current device 
     */
    pGEDevDesc dd = getDevice();
    setGridStateElement(dd, GSS_GPSAVED, gpars);
    return R_NilValue;
}

void initGPar(pGEDevDesc dd)
{
    pDevDesc dev = dd->dev;
    SEXP gpar, gparnames, class;
    SEXP gpfill, gpcol, gpgamma, gplty, gplwd, gpcex, gpfs, gplh, gpfont;
    SEXP gpfontfamily, gpalpha, gplineend, gplinejoin, gplinemitre, gplex;
    SEXP gsd = (SEXP) dd->gesd[gridRegisterIndex]->systemSpecific;
    PROTECT(gpar = allocVector(VECSXP, 15));
    PROTECT(gparnames = allocVector(STRSXP, 15));
    SET_STRING_ELT(gparnames, GP_FILL, mkChar("fill"));
    SET_STRING_ELT(gparnames, GP_COL, mkChar("col"));
    SET_STRING_ELT(gparnames, GP_GAMMA, mkChar("gamma"));
    SET_STRING_ELT(gparnames, GP_LTY, mkChar("lty"));
    SET_STRING_ELT(gparnames, GP_LWD, mkChar("lwd"));
    SET_STRING_ELT(gparnames, GP_CEX, mkChar("cex"));
    SET_STRING_ELT(gparnames, GP_FONTSIZE, mkChar("fontsize"));
    SET_STRING_ELT(gparnames, GP_LINEHEIGHT, mkChar("lineheight"));
    SET_STRING_ELT(gparnames, GP_FONT, mkChar("font"));
    SET_STRING_ELT(gparnames, GP_FONTFAMILY, mkChar("fontfamily"));
    SET_STRING_ELT(gparnames, GP_ALPHA, mkChar("alpha"));
    SET_STRING_ELT(gparnames, GP_LINEEND, mkChar("lineend"));
    SET_STRING_ELT(gparnames, GP_LINEJOIN, mkChar("linejoin"));
    SET_STRING_ELT(gparnames, GP_LINEMITRE, mkChar("linemitre"));
    SET_STRING_ELT(gparnames, GP_LEX, mkChar("lex"));
    setAttrib(gpar, R_NamesSymbol, gparnames);
    PROTECT(gpfill = allocVector(STRSXP, 1));
    SET_STRING_ELT(gpfill, 0, mkChar(col2name(dev->startfill)));
    SET_VECTOR_ELT(gpar, GP_FILL, gpfill);
    PROTECT(gpcol = allocVector(STRSXP, 1));
    SET_STRING_ELT(gpcol, 0, mkChar(col2name(dev->startcol)));
    SET_VECTOR_ELT(gpar, GP_COL, gpcol);
    PROTECT(gpgamma = allocVector(REALSXP, 1));
    REAL(gpgamma)[0] = dev->startgamma;
    SET_VECTOR_ELT(gpar, GP_GAMMA, gpgamma);
    PROTECT(gplty = GE_LTYget(dev->startlty));
    SET_VECTOR_ELT(gpar, GP_LTY, gplty);
    PROTECT(gplwd = allocVector(REALSXP, 1));
    REAL(gplwd)[0] = 1;
    SET_VECTOR_ELT(gpar, GP_LWD, gplwd);
    PROTECT(gpcex = allocVector(REALSXP, 1));
    REAL(gpcex)[0] = 1;
    SET_VECTOR_ELT(gpar, GP_CEX, gpcex);
    PROTECT(gpfs = allocVector(REALSXP, 1));
    REAL(gpfs)[0] = dev->startps;
    SET_VECTOR_ELT(gpar, GP_FONTSIZE, gpfs);
    PROTECT(gplh = allocVector(REALSXP, 1));
    REAL(gplh)[0] = 1.2;
    SET_VECTOR_ELT(gpar, GP_LINEHEIGHT, gplh);
    PROTECT(gpfont = allocVector(INTSXP, 1));
    INTEGER(gpfont)[0] = dev->startfont;
    SET_VECTOR_ELT(gpar, GP_FONT, gpfont);
    PROTECT(gpfontfamily = allocVector(STRSXP, 1));
    /* 
     * A font family of "" means that the default font
     * set up by the device will be used.
     */
    SET_STRING_ELT(gpfontfamily, 0, mkChar(""));
    SET_VECTOR_ELT(gpar, GP_FONTFAMILY, gpfontfamily);
    PROTECT(gpalpha = allocVector(REALSXP, 1));
    REAL(gpalpha)[0] = 1;
    SET_VECTOR_ELT(gpar, GP_ALPHA, gpalpha);
    PROTECT(gplineend = allocVector(STRSXP, 1));
    SET_STRING_ELT(gplineend, 0, mkChar("round"));
    SET_VECTOR_ELT(gpar, GP_LINEEND, gplineend);
    PROTECT(gplinejoin = allocVector(STRSXP, 1));
    SET_STRING_ELT(gplinejoin, 0, mkChar("round"));
    SET_VECTOR_ELT(gpar, GP_LINEJOIN, gplinejoin);
    PROTECT(gplinemitre = allocVector(REALSXP, 1));
    REAL(gplinemitre)[0] = 10;
    SET_VECTOR_ELT(gpar, GP_LINEMITRE, gplinemitre);
    PROTECT(gplex = allocVector(REALSXP, 1));
    REAL(gplex)[0] = 1;
    SET_VECTOR_ELT(gpar, GP_LEX, gplex);
    PROTECT(class = allocVector(STRSXP, 1));
    SET_STRING_ELT(class, 0, mkChar("gpar"));
    classgets(gpar, class);
    SET_VECTOR_ELT(gsd, GSS_GPAR, gpar);
    UNPROTECT(18);
}

// Minimal primitive gc setters
// These should only be called after gcontextFromgpar has been called once
void initGContext(SEXP gp, const pGEcontext gc, pGEDevDesc dd, int* gpIsScalar, 
                  const pGEcontext gcCache) 
{
    int i = 0;
    /* 
     * Combine gpAlpha with col and fill
     */
    gcCache->col = gc->col = 
        combineAlpha(gpAlpha2(gp, i, gpIsScalar), gpCol2(gp, i, gpIsScalar));
    gcCache->fill = gc->fill = 
        combineAlpha(gpAlpha(gp, i), gpFill2(gp, i, gpIsScalar));
    gcCache->gamma = gc->gamma = gpGamma2(gp, i, gpIsScalar);
    /*
     * Combine gpLex with lwd
     * Also scale by GSS_SCALE (a "zoom" factor)
     */
    gcCache->lwd = gc->lwd = gpLineWidth2(gp, i, gpIsScalar) * 
        gpLex2(gp, i, gpIsScalar) * REAL(gridStateElement(dd, GSS_SCALE))[0];
    gcCache->lty = gc->lty = gpLineType2(gp, i, gpIsScalar);
    gcCache->lend = gc->lend = gpLineEnd2(gp, i, gpIsScalar);
    gcCache->ljoin = gc->ljoin = gpLineJoin2(gp, i, gpIsScalar);
    gcCache->lmitre = gc->lmitre = gpLineMitre2(gp, i, gpIsScalar);
    gcCache->cex = gc->cex = gpCex2(gp, i, gpIsScalar);
    /*
     * Scale by GSS_SCALE (a "zoom" factor)
     */
    gcCache->ps = gc->ps = gpFontSize2(gp, i, gpIsScalar) * 
        REAL(gridStateElement(dd, GSS_SCALE))[0];
    gcCache->lineheight = gc->lineheight = gpLineHeight2(gp, i, gpIsScalar);
    gcCache->fontface = gc->fontface = gpFont2(gp, i, gpIsScalar);
    strcpy(gc->fontfamily, gpFontFamily2(gp, i, gpIsScalar));
    strcpy(gcCache->fontfamily, gc->fontfamily);
}
void updateGContext(SEXP gp, int i, const pGEcontext gc, pGEDevDesc dd, 
                    int* gpIsScalar, const pGEcontext gcCache)
{
    if (gpIsScalar[0] == -1) {
        error(_("updateGContext must only be called after initGContext"));
    }
    if (!(gpIsScalar[GP_ALPHA] && gpIsScalar[GP_COL])) {
        double alpha = gpAlpha(gp, i);
        if (alpha == 1.0) gc->col = gpCol(gp, i);
        else gc->col = combineAlpha(alpha, gpCol(gp, i));
    } else {
        gc->col = gcCache->col;
    }
    if (!(gpIsScalar[GP_ALPHA] && gpIsScalar[GP_FILL])) {
        double alpha = gpAlpha(gp, i);
        if (alpha == 1.0) gc->fill = gpFill(gp, i);
        else gc->fill = combineAlpha(alpha, gpFill(gp, i));
    } else {
        gc->fill = gcCache->fill;
    }
    gc->gamma = gpIsScalar[GP_GAMMA] ? gcCache->gamma : gpGamma(gp, i);
    gc->lwd = (gpIsScalar[GP_LWD] && gpIsScalar[GP_LEX]) ? gcCache->lwd :
        gpLineWidth(gp, i) * gpLex(gp, i) * 
            REAL(gridStateElement(dd, GSS_SCALE))[0];
    gc->lty = gpIsScalar[GP_LTY] ? gcCache->lty : gpLineType(gp, i);
    gc->lend = gpIsScalar[GP_LINEEND] ? gcCache->lend : gpLineEnd(gp, i);
    gc->ljoin = gpIsScalar[GP_LINEJOIN] ? gcCache->ljoin : gpLineJoin(gp, i);
    gc->lmitre = gpIsScalar[GP_LINEMITRE] ? gcCache->lmitre : gpLineMitre(gp, i);
    gc->cex = gpIsScalar[GP_CEX] ? gcCache->cex : gpCex(gp, i);
    gc->ps = gpIsScalar[GP_FONTSIZE] ? gcCache->ps : gpFontSize(gp, i) * 
        REAL(gridStateElement(dd, GSS_SCALE))[0];
    gc->lineheight = gpIsScalar[GP_LINEHEIGHT] ? gcCache->lineheight :
        gpLineHeight(gp, i);
    gc->fontface = gpIsScalar[GP_FONT] ? gcCache->fontface : gpFont(gp, i);
    if (gpIsScalar[GP_FONTFAMILY]) {
        strcpy(gc->fontfamily, gcCache->fontfamily);
    } else {
        strcpy(gc->fontfamily, gpFontFamily(gp, i));
    }
}
