/*
 * << Haru Free PDF Library >> -- hpdf_shading.c
 *
 * URL: http://libharu.org
 *
 * Copyright (c) 1999-2006 Takeshi Kanno <takeshi_kanno@est.hi-ho.ne.jp>
 * Copyright (c) 2007-2009 Antony Dovgal <tony@daylessday.org>
 * Copyright (c) 2017 Kitware <kitware@kitware.com>
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 * It is provided "as is" without express or implied warranty.
 *
 */

#include "hpdf.h"
#include "hpdf_utils.h"

#include "assert.h"

typedef struct _RGBVertex
{
  HPDF_UINT8 EdgeFlag;
  HPDF_UINT32 X;
  HPDF_UINT32 Y;
  HPDF_UINT8 RGB[3];
} RGBVertex;

static const char *COL_CMYK = "DeviceCMYK";
static const char *COL_RGB = "DeviceRGB";
static const char *COL_GRAY = "DeviceGray";

/* bbox is filled with xMin, xMax, yMin, yMax */
static HPDF_BOOL _GetDecodeArrayVertexValues(HPDF_Shading shading,
                                             HPDF_REAL *bbox)
{
  HPDF_Array decodeArray;
  HPDF_Real r;
  int i;

  if (!shading) {
    return HPDF_FALSE;
  }

  decodeArray = (HPDF_Array)(HPDF_Dict_GetItem(shading, "Decode",
                                               HPDF_OCLASS_ARRAY));
  if (!decodeArray) {
    return HPDF_FALSE;
  }

  for (i = 0; i < 4; ++i)
  {
    r = HPDF_Array_GetItem(decodeArray, i, HPDF_OCLASS_REAL);
    if (!r) {
      return HPDF_FALSE;
    }

    bbox[i] = r->value;
  }

  return HPDF_TRUE;
}

static void UINT32Swap (HPDF_UINT32  *value)
{
  HPDF_BYTE b[4];

  HPDF_MemCpy (b, (HPDF_BYTE *)value, 4);
  *value = (HPDF_UINT32)((HPDF_UINT32)b[0] << 24 |
           (HPDF_UINT32)b[1] << 16 |
           (HPDF_UINT32)b[2] << 8 |
           (HPDF_UINT32)b[3]);
}

/* Encode a position coordinate for writing */
static HPDF_UINT32 _EncodeValue(HPDF_REAL x, HPDF_REAL xMin, HPDF_REAL xMax)
{
  HPDF_DOUBLE norm = (x - xMin) / (xMax - xMin);
  HPDF_DOUBLE max = (HPDF_DOUBLE)(0xFFFFFFFF);
  HPDF_UINT32 enc = (HPDF_UINT32)(norm * max);
  UINT32Swap(&enc);
  return enc;
}

static HPDF_Array HPDF_FloatArray  (HPDF_Doc    pdf,   
                                    float*      values,
                                    HPDF_UINT32 count,
                                    HPDF_UINT32 repeat)
{
  int i, j;
  HPDF_Array floatArray = HPDF_Array_New(pdf->mmgr);
  HPDF_STATUS ret = HPDF_OK;
  if (!floatArray) {
    return NULL;
  }
  for (j = 0; j < repeat; ++j)
  {
    for (i = 0; i < count; ++i)
    {
      ret += HPDF_Array_AddReal(floatArray, values[i]);
    }
  }
  return (ret != HPDF_OK) ? NULL : floatArray;
}

static HPDF_Array HPDF_ColorArray  (HPDF_Doc    pdf,   
                                    HPDF_UINT32 color,
                                    HPDF_REAL   opacity)
{
  float cols[] = { 
    ((color >> 16) & 0xFF) / 255.0,
    ((color >>  8) & 0xFF) / 255.0,
    ((color >>  0) & 0xFF) / 255.0,
    opacity
  };
  return HPDF_FloatArray(pdf, cols, 4, 1);
}

static HPDF_STATUS HPDF_ColorFunction  (HPDF_Doc                 pdf,
                                        HPDF_Dict                colorFunction,
                                        const HPDF_GradientStop* stop1,
                                        const HPDF_GradientStop* stop2)
{
  float domain[] = { 0.0, 1.0 };
  HPDF_STATUS ret = HPDF_OK;
  ret += HPDF_Dict_AddNumber(colorFunction, "FunctionType", 2);
  ret += HPDF_Dict_Add(colorFunction, "Domain", HPDF_FloatArray(pdf, domain, 2, 1));
  ret += HPDF_Dict_Add(colorFunction, "C0",     HPDF_ColorArray(pdf, stop1->color, stop1->opacity));
  ret += HPDF_Dict_Add(colorFunction, "C1",     HPDF_ColorArray(pdf, stop2->color, stop2->opacity));
  ret += HPDF_Dict_AddNumber(colorFunction, "N", 1);
  return ret;
}

HPDF_EXPORT(HPDF_Shading)
HPDF_LinearGradient_New  (HPDF_Doc                 pdf,
                          const HPDF_GradientStop* stops,
                          HPDF_UINT                num_stops,
                          HPDF_REAL                x1,
                          HPDF_REAL                y1,
                          HPDF_REAL                x2,
                          HPDF_REAL                y2)
{
  HPDF_STATUS ret = HPDF_OK;
  HPDF_Shading shading;
  HPDF_Array extendArray;
  HPDF_Dict colorFunction;
  float domain[] = { 0.0, 1.0 };
  float coords[] = { x1, y1, x2, y2 };
  int i;

  HPDF_PTRACE((" HPDF_LinearGradient_New\n"));

  if (num_stops < 2) {
    return NULL;
  }

  if (!HPDF_HasDoc(pdf)) {
    return NULL;
  }

  shading = HPDF_Dict_New(pdf->mmgr);
  if (!shading) {
    return NULL;
  }

  colorFunction = HPDF_Dict_New(pdf->mmgr);
  if (!colorFunction) {
    return NULL;
  }

  extendArray = HPDF_Array_New(pdf->mmgr);
  if (!extendArray) {
    return NULL;
  }

  ret += HPDF_Array_AddBoolean(extendArray, HPDF_TRUE);
  ret += HPDF_Array_AddBoolean(extendArray, HPDF_TRUE);

  if (num_stops == 2)
  {
    ret += HPDF_ColorFunction(pdf, colorFunction, &stops[0], &stops[1]);
  }
  else
  {
    ret += HPDF_Dict_AddNumber(colorFunction, "FunctionType", 3);

    // add bounds to color function
    HPDF_Array boundsArray = HPDF_Array_New(pdf->mmgr);
    if (!boundsArray) {
      return NULL;
    }
    for (i = 1; i < num_stops - 1; ++i)
    {
      HPDF_Array_AddReal(boundsArray, stops[i].offset);
    }
    ret += HPDF_Dict_Add(colorFunction, "Bounds", boundsArray);
    
    // add functions to color function
    HPDF_Array functionsArray = HPDF_Array_New(pdf->mmgr);
    if (!functionsArray) {
      return NULL;
    }
    for (i = 0; i < num_stops - 1; ++i)
    {
      HPDF_Dict colorFunction2 = HPDF_Dict_New(pdf->mmgr);
      if (!colorFunction2) {
        return NULL;
      }
      ret += HPDF_ColorFunction(pdf, colorFunction2, &stops[i+0], &stops[i+1]);
      HPDF_Array_Add(functionsArray, colorFunction2);
    }
    ret += HPDF_Dict_Add(colorFunction, "Functions", functionsArray);
    
    // add domain and encode to color function
    ret += HPDF_Dict_Add(colorFunction, "Domain", HPDF_FloatArray(pdf, domain, 2, 1));
    ret += HPDF_Dict_Add(colorFunction, "Encode", HPDF_FloatArray(pdf, domain, 2, num_stops - 1));
  }

  shading->header.obj_class |= HPDF_OSUBCLASS_SHADING;
  ret += HPDF_Dict_AddNumber(shading, "ShadingType", HPDF_SHADING_LINEAR);
  ret += HPDF_Dict_AddName(shading, "ColorSpace", COL_RGB);
  ret += HPDF_Dict_Add(shading, "Coords", HPDF_FloatArray(pdf, coords, 4, 1));
  ret += HPDF_Dict_Add(shading, "Domain", HPDF_FloatArray(pdf, domain, 2, 1));
  ret += HPDF_Dict_Add(shading, "Extend", extendArray);
  ret += HPDF_Dict_Add(shading, "Function", colorFunction);

  if (ret != HPDF_OK) {
    return NULL;
  }

  return shading;
}

HPDF_EXPORT(HPDF_Shading)
HPDF_Shading_New  (HPDF_Doc         pdf,
                   HPDF_ShadingType type,
                   HPDF_ColorSpace  colorSpace,
                   HPDF_REAL xMin, HPDF_REAL xMax,
                   HPDF_REAL yMin, HPDF_REAL yMax)
{
  HPDF_Shading shading;
  HPDF_Array decodeArray;
  HPDF_STATUS ret = HPDF_OK;
  int i;

  HPDF_PTRACE((" HPDF_Shading_New\n"));

  if (!HPDF_HasDoc(pdf)) {
    return NULL;
  }

  /* Validate shading type: */
  switch (type)
  {
    case HPDF_SHADING_FREE_FORM_TRIANGLE_MESH:
      break;

    default:
      HPDF_SetError (pdf->mmgr->error, HPDF_INVALID_SHADING_TYPE, 0);
      return NULL;
  }

  decodeArray = HPDF_Array_New(pdf->mmgr);
  if (!decodeArray) {
    return NULL;
  }

  /* X-range */
  ret += HPDF_Array_AddReal(decodeArray, xMin);
  ret += HPDF_Array_AddReal(decodeArray, xMax);

  /* Y-range */
  ret += HPDF_Array_AddReal(decodeArray, yMin);
  ret += HPDF_Array_AddReal(decodeArray, yMax);

  const char *colName = NULL;
  switch (colorSpace) {
    case HPDF_CS_DEVICE_RGB:
      colName = COL_RGB;
      for (i = 0; i < 3; ++i) {
        ret += HPDF_Array_AddReal(decodeArray, 0.0);
        ret += HPDF_Array_AddReal(decodeArray, 1.0);
      }
      break;

    default:
      HPDF_SetError(pdf->mmgr->error, HPDF_INVALID_COLOR_SPACE, 0);
      return NULL;
  }

  if (ret != HPDF_OK) {
    return NULL;
  }

  shading = HPDF_DictStream_New(pdf->mmgr, pdf->xref);
  if (!shading) {
    return NULL;
  }

  shading->header.obj_class |= HPDF_OSUBCLASS_SHADING;
  ret += HPDF_Dict_AddNumber(shading, "ShadingType", type);
  ret += HPDF_Dict_AddName(shading, "ColorSpace", colName);

  switch (type)
  {
    case HPDF_SHADING_FREE_FORM_TRIANGLE_MESH:
      ret += HPDF_Dict_AddNumber(shading, "BitsPerCoordinate", 32);
      ret += HPDF_Dict_AddNumber(shading, "BitsPerComponent", 8);
      ret += HPDF_Dict_AddNumber(shading, "BitsPerFlag", 8);
      ret += HPDF_Dict_Add(shading, "Decode", decodeArray);
      break;

    default:
      HPDF_SetError (pdf->mmgr->error, HPDF_INVALID_SHADING_TYPE, 0);
      return NULL;
  }

  if (ret != HPDF_OK) {
    return NULL;
  }

  return shading;
}

HPDF_EXPORT(HPDF_STATUS)
HPDF_Shading_AddVertexRGB(HPDF_Shading shading,
                          HPDF_Shading_FreeFormTriangleMeshEdgeFlag edgeFlag,
                          HPDF_REAL x, HPDF_REAL y,
                          HPDF_UINT8 r, HPDF_UINT8 g, HPDF_UINT8 b)
{
  HPDF_STATUS ret = HPDF_OK;
  RGBVertex vert;
  float bbox[4];

  HPDF_PTRACE((" HPDF_Shading_AddVertexRGB\n"));

  if (!shading) {
    return HPDF_INVALID_OBJECT;
  }

  if (_GetDecodeArrayVertexValues(shading, bbox) != HPDF_TRUE) {
    return HPDF_SetError(shading->error, HPDF_INVALID_OBJECT, 0);
  }

  vert.EdgeFlag = (HPDF_UINT8)edgeFlag;
  vert.X = _EncodeValue(x, bbox[0], bbox[1]);
  vert.Y = _EncodeValue(y, bbox[2], bbox[3]);
  vert.RGB[0] = r;
  vert.RGB[1] = g;
  vert.RGB[2] = b;

  ret = HPDF_Stream_Write(shading->stream,
                          (HPDF_BYTE*)(&vert.EdgeFlag), sizeof(vert.EdgeFlag));
  if (ret != HPDF_OK)
  {
    return ret;
  }

  ret = HPDF_Stream_Write(shading->stream,
                          (HPDF_BYTE*)(&vert.X), sizeof(vert.X));
  if (ret != HPDF_OK)
  {
    return ret;
  }

  ret = HPDF_Stream_Write(shading->stream,
                          (HPDF_BYTE*)(&vert.Y), sizeof(vert.Y));
  if (ret != HPDF_OK)
  {
    return ret;
  }

  ret = HPDF_Stream_Write(shading->stream,
                          (HPDF_BYTE*)(&vert.RGB), sizeof(vert.RGB));

  return ret;
}

