/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  UTFGrid rendering functions (using AGG)
 * Author:   Francois Desjarlais
 *
 ******************************************************************************
 * Copyright (c) 1996-2007 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/


#include "mapserver.h"
#include "maputfgrid.h"
#include "mapagg.h"
#include "renderers/agg/include/agg_rasterizer_scanline_aa.h"
#include "renderers/agg/include/agg_basics.h"
#include "renderers/agg/include/agg_renderer_scanline.h"
#include "renderers/agg/include/agg_scanline_bin.h"
#include "renderers/agg/include/agg_gamma_functions.h"
#include "renderers/agg/include/agg_conv_stroke.h"
#include "renderers/agg/include/agg_ellipse.h"

#include <memory>
#include <vector>

typedef mapserver::int32u band_type;
typedef mapserver::row_ptr_cache<band_type> rendering_buffer;
typedef pixfmt_utf<utfpix32, rendering_buffer> pixfmt_utf32;
typedef mapserver::renderer_base<pixfmt_utf32> renderer_base;
typedef mapserver::rasterizer_scanline_aa<> rasterizer_scanline;
typedef mapserver::renderer_scanline_bin_solid<renderer_base> renderer_scanline;

static const utfpix32 UTF_WATER = utfpix32(32);

#define utfitem(c) utfpix32(c)

namespace {

struct shapeData
{
  std::string datavalues{};
  std::string itemvalue{};
  band_type utfvalue = 0;
  int serialid = 0;
};

/*
 * UTFGrid specific polygon adaptor.
 */
class polygon_adaptor_utf final: public polygon_adaptor
{
public:
  polygon_adaptor_utf(shapeObj *shape,int utfres_in):
      polygon_adaptor(shape),
      utfresolution(utfres_in)
  {
  }

  unsigned vertex(double* x, double* y) override
  {
    if(m_point < m_pend) {
      bool first = m_point == m_line->point;
      *x = m_point->x/utfresolution;
      *y = m_point->y/utfresolution;
      m_point++;
      return first ? mapserver::path_cmd_move_to : mapserver::path_cmd_line_to;
    }
    *x = *y = 0.0;
    if(!m_stop) {

      m_line++;
      if(m_line>=m_lend) {
        m_stop=true;
        return mapserver::path_cmd_end_poly;
      }

      m_point=m_line->point;
      m_pend=&(m_line->point[m_line->numpoints]);
      return mapserver::path_cmd_end_poly;
    }
    return mapserver::path_cmd_stop;
  }

private:
  int utfresolution;
};

/*
 * UTFGrid specific line adaptor.
 */
class line_adaptor_utf final: public line_adaptor
{
public:
  line_adaptor_utf(shapeObj *shape,int utfres_in):
      line_adaptor(shape),
      utfresolution(utfres_in)
  {
  }

  unsigned vertex(double* x, double* y) override
  {
    if(m_point < m_pend) {
      bool first = m_point == m_line->point;
      *x = m_point->x/utfresolution;
      *y = m_point->y/utfresolution;
      m_point++;
      return first ? mapserver::path_cmd_move_to : mapserver::path_cmd_line_to;
    }
    m_line++;
    *x = *y = 0.0;
    if(m_line>=m_lend)
      return mapserver::path_cmd_stop;

    m_point=m_line->point;
    m_pend=&(m_line->point[m_line->numpoints]);

    return vertex(x,y);
  }

private:
  int utfresolution;
};

class UTFGridRenderer {
public:
  std::vector<shapeData> table{};
  int utfresolution = 0;
  int layerwatch = 0;
  bool renderlayer = false;
  bool useutfitem = false;
  bool useutfdata = false;
  bool duplicates = false;
  band_type utfvalue = 0;
  layerObj *utflayer = nullptr;
  int width = 0;
  int height = 0;
  std::vector<band_type> buffer{};
  rendering_buffer m_rendering_buffer{};
  pixfmt_utf32 m_pixel_format{};
  renderer_base m_renderer_base{};
  rasterizer_scanline m_rasterizer{};
  renderer_scanline m_renderer_scanline{};
  mapserver::scanline_bin sl_utf{};
  std::unique_ptr<mapserver::conv_stroke<line_adaptor_utf>> stroke{};
};

} // namespace

#define UTFGRID_RENDERER(image) (static_cast<UTFGridRenderer*>((image)->img.plugin))

/*
 * Encode to avoid unavailable char in the JSON
 */
static
unsigned int encodeForRendering(unsigned int toencode)
{
  unsigned int encoded;
  encoded = toencode + 32;
  /* 34 => " */
  if(encoded >= 34) {
    encoded = encoded +1;
  }
  /* 92 => \ */
  if (encoded >= 92) {
    encoded = encoded +1;
  }
  return encoded;
}

/*
 * Decode value to have the initial one
 */
static
unsigned int decodeRendered(unsigned int todecode)
{
  unsigned int decoded;
  
  if(todecode >= 92)
    todecode --;

  if(todecode >= 34)
    todecode --;

  decoded = todecode-32;

  return decoded;
}

/*
 * Add the shapeObj UTFDATA and UTFITEM to the lookup table.
 */
static
band_type addToTable(UTFGridRenderer *r, shapeObj *p)
{
  /* Looks for duplicates. */
  if(r->duplicates == false && r->useutfitem) {
    const auto itemvalue = p->values[r->utflayer->utfitemindex];
    for( const auto& tableItem: r->table ) {
      if(tableItem.itemvalue == itemvalue) {
        /* Found a copy of the values in the table. */
        return tableItem.utfvalue;
      }
    }
  }

  /* Data added to the table */
  shapeData table;
  char* escaped = msEvalTextExpressionJSonEscape(&r->utflayer->utfdata, p);
  table.datavalues = escaped ? escaped : "";
  msFree(escaped);

  /* If UTFITEM is set in the mapfile we add its value to the table */
  if(r->useutfitem)
    table.itemvalue = p->values[r->utflayer->utfitemindex];

  table.serialid = static_cast<int>(r->table.size()+1);

  /* Simple operation so we don't have unavailable char in the JSON */
  const auto utfvalue = encodeForRendering(table.serialid);
  table.utfvalue = utfvalue;

  r->table.emplace_back(std::move(table));

  return utfvalue;
}

/*
 * Use AGG to render any path.
 */
template<class vertex_source>
static int utfgridRenderPath(imageObj *img, vertex_source &path)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);
  r->m_rasterizer.reset();
  r->m_rasterizer.filling_rule(mapserver::fill_even_odd);
  r->m_rasterizer.add_path(path);
  r->m_renderer_scanline.color(utfitem(r->utfvalue));
  mapserver::render_scanlines(r->m_rasterizer, r->sl_utf, r->m_renderer_scanline);
  return MS_SUCCESS;
}

/*
 * Initialize the renderer, create buffer, allocate memory.
 */
static
imageObj *utfgridCreateImage(int width, int height, outputFormatObj *format, colorObj * /*bg*/)
{
  auto r = std::unique_ptr<UTFGridRenderer>(new UTFGridRenderer);

  r->utfresolution = atof(msGetOutputFormatOption(format, "UTFRESOLUTION", "4"));
  if(r->utfresolution < 1) {
    msSetError(MS_MISCERR, "UTFRESOLUTION smaller that 1 in the mapfile.", "utfgridCreateImage()");
    return nullptr;
  }
  r->duplicates = EQUAL("true", msGetOutputFormatOption(format, "DUPLICATES", "true"));

  r->width = width/r->utfresolution;
  r->height = height/r->utfresolution;

  r->buffer.resize(r->width * r->height);

  /* AGG specific operations */
  r->m_rendering_buffer.attach(&r->buffer[0], r->width, r->height, r->width);
  r->m_pixel_format.attach(r->m_rendering_buffer);
  r->m_renderer_base.attach(r->m_pixel_format);
  r->m_renderer_scanline.attach(r->m_renderer_base);
  r->m_renderer_base.clear(UTF_WATER);
  r->m_rasterizer.gamma(mapserver::gamma_none());

  imageObj *image = (imageObj *) msSmallCalloc(1,sizeof(imageObj));
  image->img.plugin = r.release();

  return image;
}

/*
 * Free all the memory used by the renderer.
 */
static
int utfgridFreeImage(imageObj *img)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);
  delete r;

  img->img.plugin = NULL;

  return MS_SUCCESS;
}

/*
 * Update a character in the utfgrid.
*/
static
int utfgridUpdateChar(imageObj *img, band_type oldChar, band_type newChar)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);

  for( auto& ch: r->buffer ) {
    if(ch == oldChar) {
      ch = newChar;
    }
  }

  return MS_SUCCESS;
}

/*
 * Remove unnecessary data that didn't made it to the final grid.
 */
static
int utfgridCleanData(imageObj *img)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);

  std::vector<bool> usedChar;
  usedChar.resize(r->table.size());

  int itemFound=0;

  for(const auto ch: r->buffer)
  {
    const auto rendered = decodeRendered(ch);
    if(rendered != 0 && !usedChar[rendered-1])
    {
      itemFound++;
      usedChar[rendered-1] = true;
    }
  }

  std::vector<shapeData> updatedData;
  updatedData.reserve(itemFound);

  for(const auto& tableItem: r->table) {
    if(usedChar[decodeRendered(tableItem.utfvalue)-1]){
        updatedData.emplace_back(tableItem);
        auto& newData = updatedData.back();

        newData.serialid=static_cast<int>(updatedData.size());

        const band_type utfvalue=encodeForRendering(newData.serialid);

        utfgridUpdateChar(img,newData.utfvalue,utfvalue);
        newData.utfvalue = utfvalue;
    }
  }

  r->table = std::move(updatedData);

  return MS_SUCCESS;
}

/*
 * Print the renderer data as JSON.
 */
static
int utfgridSaveImage(imageObj *img, mapObj * /*map*/, FILE *fp, outputFormatObj * /*format*/)
{
  utfgridCleanData(img);

  const UTFGridRenderer *renderer = UTFGRID_RENDERER(img);

  if(renderer->layerwatch>1)
    return MS_FAILURE;

  const int imgheight = renderer->height;
  const int imgwidth = renderer->width;

  msIO_fprintf(fp,"{\"grid\":[");

  /* Print the buffer */
  std::vector<wchar_t> string;
  string.resize(imgwidth+1);
  for(int row=0; row<imgheight; row++) {

    /* Need a comma between each line but JSON must not start with a comma. */
    if(row!=0)
      msIO_fprintf(fp,",");
    msIO_fprintf(fp,"\"");
    for(int col=0; col<imgwidth; col++) {
      /* Get the data from buffer. */
      band_type pixelid = renderer->buffer[(row*imgwidth)+col];
      string[col] = pixelid;
    }

    /* Conversion to UTF-8 encoding */
#if defined(_WIN32) && !defined(__CYGWIN__)
	const char* encoding = "UCS-2LE";
#else
	const char* encoding = "UCS-4LE";
#endif

    char* utf8 = msConvertWideStringToUTF8(string.data(), encoding);
    msIO_fprintf(fp,"%s", utf8);
    msFree(utf8);
    msIO_fprintf(fp,"\"");
  }

  msIO_fprintf(fp,"],\"keys\":[\"\"");

  /* Print the specified key */
  for(const auto& table: renderer->table) {
      msIO_fprintf(fp,",");

    if(renderer->useutfitem)
    {
      char* pszEscaped = msEscapeJSonString(table.itemvalue.c_str());
      msIO_fprintf(fp,"\"%s\"", pszEscaped);
      msFree(pszEscaped);
    }
    /* If no UTFITEM specified use the serial ID as the key */
    else
      msIO_fprintf(fp,"\"%i\"", table.serialid);
  }

  msIO_fprintf(fp,"],\"data\":{");

  /* Print the data */
  if(renderer->useutfdata) {
    bool first = true;
    for(const auto& table: renderer->table) {
      if(!first)
        msIO_fprintf(fp,",");
      first = false;

      if(renderer->useutfitem)
      {
        char* pszEscaped = msEscapeJSonString(table.itemvalue.c_str());
        msIO_fprintf(fp,"\"%s\":", pszEscaped);
        msFree(pszEscaped);
      }
      /* If no UTFITEM specified use the serial ID as the key */
      else
        msIO_fprintf(fp,"\"%i\":", table.serialid);

      msIO_fprintf(fp,"%s", table.datavalues.c_str());
    }
  }
  msIO_fprintf(fp,"}}");

  return MS_SUCCESS;
}

/*
 * Starts a layer for UTFGrid renderer.
 */
static
int utfgridStartLayer(imageObj *img, mapObj * /*map*/, layerObj *layer)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);

  /* Look if the layer uses the UTFGrid output format */
  if(layer->utfdata.string!=0) {
    r->useutfdata = true;
  }

    /* layerwatch is set to 1 on first layer treated. Doesn't allow multiple layers. */
    if(!r->layerwatch) {
      r->layerwatch++;

      r->renderlayer = true;
      r->utflayer = layer;
      layer->refcount++;

      /* Verify if renderer needs to use UTFITEM */
      if(r->utflayer->utfitem)
        r->useutfitem = true;
    }
    /* If multiple layers, send error */
    else {
      r->layerwatch++;
      msSetError(MS_MISCERR, "MapServer does not support multiple UTFGrid layers rendering simultaneously.", "utfgridStartLayer()");
      return MS_FAILURE;
    }

  return MS_SUCCESS;
}

/*
 * Tell renderer the layer is done.
 */
static
int utfgridEndLayer(imageObj *img, mapObj * /*map*/, layerObj *layer)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);

  /* Look if the layer was rendered, if it was then turn off rendering. */
  if(r->renderlayer) {
    r->utflayer = NULL;
    layer->refcount--;
    r->renderlayer = false;
  }

  return MS_SUCCESS;
}

/*
 * Do the table operations on the shapes. Allow multiple types of data to be rendered.
 */
static
int utfgridStartShape(imageObj *img, shapeObj *shape)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);

  if(!r->renderlayer)
    return MS_FAILURE;

  /* Table operations */
  r->utfvalue = addToTable(r, shape);

  return MS_SUCCESS;
}

/*
 * Tells the renderer that the shape's rendering is done.
 */
static
int utfgridEndShape(imageObj *img, shapeObj *)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);

  r->utfvalue = 0;
  return MS_SUCCESS;
}

/*
 * Function that renders polygons into UTFGrid.
 */
static
int utfgridRenderPolygon(imageObj *img, shapeObj *polygonshape, colorObj *)
{
  const UTFGridRenderer *r = UTFGRID_RENDERER(img);

  /* utfvalue is set to 0 if the shape isn't in the table. */
  if(r->utfvalue == 0) {
    return MS_FAILURE;
  }

  /* Render the polygon */
  polygon_adaptor_utf polygons(polygonshape, r->utfresolution);
  utfgridRenderPath(img, polygons);

  return MS_SUCCESS;
}

/*
 * Function that renders lines into UTFGrid. Starts by looking if the line is a polygon
 * outline, draw it if it's not.
 */
static
int utfgridRenderLine(imageObj *img, shapeObj *lineshape, strokeStyleObj *linestyle)
{
  UTFGridRenderer *r = UTFGRID_RENDERER(img);

  /* utfvalue is set to 0 if the shape isn't in the table. */
  if(r->utfvalue == 0) {
    return MS_SUCCESS;
    /* If we dont get a caracter to draw we just skip execution
     * instead of failing
     */
  }

  /* Render the line */
  line_adaptor_utf lines(lineshape, r->utfresolution);

  if(!r->stroke) {
    r->stroke.reset(new mapserver::conv_stroke<line_adaptor_utf>(lines));
  } else {
    r->stroke->attach(lines);
  }
  r->stroke->width(linestyle->width/r->utfresolution);
  utfgridRenderPath(img, *r->stroke);

  return MS_SUCCESS;
}

/*
 * Function that render vector type symbols into UTFGrid.
 */
static
int utfgridRenderVectorSymbol(imageObj *img, double x, double y, symbolObj *symbol, symbolStyleObj * style)
{
  const UTFGridRenderer *r = UTFGRID_RENDERER(img);
  double ox = symbol->sizex * 0.5;
  double oy = symbol->sizey * 0.5;

  /* utfvalue is set to 0 if the shape isn't in the table. */
  if(r->utfvalue == 0) {
    return MS_FAILURE;
  }

  /* Pathing the symbol */
  mapserver::path_storage path = imageVectorSymbol(symbol);

  /* Transformation to the right size/scale. */
  mapserver::trans_affine mtx;
  mtx *= mapserver::trans_affine_translation(-ox,-oy);
  mtx *= mapserver::trans_affine_scaling(style->scale/r->utfresolution);
  mtx *= mapserver::trans_affine_rotation(-style->rotation);
  mtx *= mapserver::trans_affine_translation(x/r->utfresolution, y/r->utfresolution);
  path.transform(mtx);

  /* Rendering the symbol. */
  utfgridRenderPath(img, path);

  return MS_SUCCESS;
}

/*
 * Function that renders Pixmap type symbols into UTFGrid.
 */
static
int utfgridRenderPixmapSymbol(imageObj *img, double x, double y, symbolObj *symbol, symbolStyleObj * style)
{
  const UTFGridRenderer *r = UTFGRID_RENDERER(img);
  rasterBufferObj *pixmap = symbol->pixmap_buffer;

  /* utfvalue is set to 0 if the shape isn't in the table. */
  if(r->utfvalue == 0) {
    return MS_FAILURE;
  }

  /* Pathing the symbol BBox */
  mapserver::path_storage pixmap_bbox;
  double w, h;
  w = pixmap->width*style->scale/(2.0);
  h= pixmap->height*style->scale/(2.0);
  pixmap_bbox.move_to((x-w)/r->utfresolution,(y-h)/r->utfresolution);
  pixmap_bbox.line_to((x+w)/r->utfresolution,(y-h)/r->utfresolution);
  pixmap_bbox.line_to((x+w)/r->utfresolution,(y+h)/r->utfresolution);
  pixmap_bbox.line_to((x-w)/r->utfresolution,(y+h)/r->utfresolution);

  /* Rendering the symbol */
  utfgridRenderPath(img, pixmap_bbox);

  return MS_SUCCESS;
}

/*
 * Function that render ellipse type symbols into UTFGrid.
 */
static
int utfgridRenderEllipseSymbol(imageObj *img, double x, double y, symbolObj *symbol, symbolStyleObj * style)
{
  const UTFGridRenderer *r = UTFGRID_RENDERER(img);

  /* utfvalue is set to 0 if the shape isn't in the table. */
  if(r->utfvalue == 0) {
    return MS_FAILURE;
  }

  /* Pathing the symbol. */
  mapserver::path_storage path;
  mapserver::ellipse ellipse(x/r->utfresolution,y/r->utfresolution,symbol->sizex*style->scale/2/r->utfresolution,symbol->sizey*style->scale/2/r->utfresolution);
  path.concat_path(ellipse);

  /* Rotation if necessary. */
  if( style->rotation != 0) {
    mapserver::trans_affine mtx;
    mtx *= mapserver::trans_affine_translation(-x/r->utfresolution,-y/r->utfresolution);
    mtx *= mapserver::trans_affine_rotation(-style->rotation);
    mtx *= mapserver::trans_affine_translation(x/r->utfresolution,y/r->utfresolution);
    path.transform(mtx);
  }

  /* Rendering the symbol. */
  utfgridRenderPath(img, path);

  return MS_SUCCESS;
}

static
int utfgridRenderGlyphs(imageObj *img, const textSymbolObj *ts, colorObj * /*c*/, colorObj * /*oc*/, int /*ow*/, int isMarker) {

  const textPathObj *tp = ts->textpath;
  const UTFGridRenderer *r = UTFGRID_RENDERER(img);

  /* If it's not a marker then it's a label or other thing and we dont
   *  want to draw it on the map
   */
  if(!isMarker) {
    return MS_SUCCESS; //Stop the rendering with no errors
  }

  /* utfvalue is set to 0 if the shape isn't in the table. */
  if(r->utfvalue == 0) {
    return MS_SUCCESS; //Stop the rendering with no errors
  }

  /* Pathing the symbol BBox */
  mapserver::path_storage box;
  double size, x, y;
  
  size = tp->glyph_size;;
  x = tp->glyphs->pnt.x;
  y = tp->glyphs->pnt.y;
  
  box.move_to((x)/r->utfresolution,(y)/r->utfresolution);
  box.line_to((x+size)/r->utfresolution,(y)/r->utfresolution);
  box.line_to((x+size)/r->utfresolution,(y-size)/r->utfresolution);
  box.line_to((x)/r->utfresolution,(y-size)/r->utfresolution);

     /* Rotation if necessary. */
  if( tp->glyphs->rot != 0) {
    mapserver::trans_affine mtx;
    mtx *= mapserver::trans_affine_translation(-x/r->utfresolution,-y/r->utfresolution);
    mtx *= mapserver::trans_affine_rotation(-tp->glyphs->rot);
    mtx *= mapserver::trans_affine_translation(x/r->utfresolution,y/r->utfresolution);
    box.transform(mtx);
  } 


  /* Rendering the symbol */
  utfgridRenderPath(img, box);

  return MS_SUCCESS;
}

static
int utfgridFreeSymbol(symbolObj *)
{
  return MS_SUCCESS;
}

/*
 * Add the necessary functions for UTFGrid to the renderer VTable.
 */
int msPopulateRendererVTableUTFGrid( rendererVTableObj *renderer )
{
  renderer->default_transform_mode = MS_TRANSFORM_SIMPLIFY;

  renderer->createImage = &utfgridCreateImage;
  renderer->freeImage = &utfgridFreeImage;
  renderer->saveImage = &utfgridSaveImage;

  renderer->startLayer = &utfgridStartLayer;
  renderer->endLayer = &utfgridEndLayer;

  renderer->startShape = &utfgridStartShape;
  renderer->endShape = &utfgridEndShape;

  renderer->renderPolygon = &utfgridRenderPolygon;
  renderer->renderGlyphs = &utfgridRenderGlyphs;
  renderer->renderLine = &utfgridRenderLine;
  renderer->renderVectorSymbol = &utfgridRenderVectorSymbol;
  renderer->renderPixmapSymbol = &utfgridRenderPixmapSymbol;
  renderer->renderEllipseSymbol = &utfgridRenderEllipseSymbol;

  renderer->freeSymbol = &utfgridFreeSymbol;

  renderer->loadImageFromFile = msLoadMSRasterBufferFromFile;

  return MS_SUCCESS;
}
