/** @file
 * libLASi provides a C++ output stream interface for writing 
 * Postscript documents containing text strings in any of the world's
 * scripts supported by Unicode 4.0 and Pango.
 * Copyright (C) 2003, 2004, 2006 by Larry Siden.
 * See README file in project root directory for copyright and contact info.
 * See COPYING file in project root for terms of re-distribution.
 */

#include <ostream>
#include <stdexcept>
#include <pango/pango.h>
#include <ctype.h>
#include <algorithm>
#include <cmath>
#include <LASi.h>
#include "contextMgr.h"
#include "glyphMgr.h"
#include "util.h"
#include "memory.h"
#include "stringDimensions.h"
#include <iomanip>

#include <stdlib.h>

using namespace std;
using namespace LASi;

static string nameof(const FT_Face& face, const FT_UInt glyph_index) 
{
  const int N = 128; // Length of buffer to hold glyph name
  const int randomNameLength = 16; // Length of a random glyph name
  char glyph_name[N];

  if (!FT_HAS_GLYPH_NAMES(face)){
    //
    // 2004.12.03.ET FIX: Since the glyph has no name entry, 
    // arbitrarily generate a string consisting of a random
    // assortment of ASCII capital letters to use: 
    //
    for(int i=0;i<randomNameLength;i++) glyph_name[i]= 65+(int)(26.0*rand()/(RAND_MAX+1.0));
    glyph_name[randomNameLength]='\0';
  }else{
    // Get the glyph name from the font when present:
    FT_Get_Glyph_Name(face, glyph_index, glyph_name, sizeof glyph_name);
  }
  return string(glyph_name);
}

PostscriptDocument::GlyphId::GlyphId(FT_Face face, const FT_UInt index) 
{
  const std::string glyphName(nameof(face, index));
  const std::string faceName(face->family_name); 
  const std::string& variant(face->style_name);
  ostringstream os;
  os << glyphName << '-' << faceName << '-' << variant << '-' << index;
  _str = os.str();
  const int len = _str.size();

  //cerr << "PostscriptDocument::GlyphId::GlyphId(...) before _str=" << _str << endl;
  // replace spaces with '-'
  for (int i=0 ; i < len ; ++i) {
    if (isspace(_str[i]))
      _str.replace(i, 1, 1, '-');
  }
  //cerr << "PostscriptDocument::GlyphId::GlyphId(...) _str=" << _str << endl;
}

PostscriptDocument::PostscriptDocument()
  : _pContextMgr(new ContextMgr()), _fontSize(10), _osBody(*this),
_osFooter(*this)
{}

PostscriptDocument::~PostscriptDocument() 
{
  delete _pContextMgr;
}

inline PangoContext* PostscriptDocument::pangoContext() const
{
  return static_cast<PangoContext*>(*_pContextMgr);
}

void PostscriptDocument::setFont(
    const char* const       family,
    LASi::FontStyle   style,
    LASi::FontWeight  weight,
    LASi::FontVariant variant,
    LASi::FontStretch stretch)
{
    
    //
    // Style:
    // 
    PangoStyle   _style;
    switch(style){
    case NORMAL_STYLE:
       _style=PANGO_STYLE_NORMAL;
       break;
    case ITALIC:
       _style=PANGO_STYLE_ITALIC;
       break;
    case OBLIQUE:
       _style=PANGO_STYLE_OBLIQUE;
       break;
    default:
       _style=PANGO_STYLE_NORMAL;
       break;    
    }
    
    //
    // Weight:
    // 
    PangoWeight  _weight;
    switch(weight){
    case NORMAL_WEIGHT:
       _weight=PANGO_WEIGHT_NORMAL;
       break;
    case BOLD:
       _weight=PANGO_WEIGHT_BOLD;
       break;
    case ULTRALIGHT:
       _weight=PANGO_WEIGHT_ULTRALIGHT;
       break;
    case LIGHT:
       _weight=PANGO_WEIGHT_LIGHT;
       break;
    case ULTRABOLD:
       _weight=PANGO_WEIGHT_ULTRABOLD;
       break;
    case HEAVY:
       _weight=PANGO_WEIGHT_HEAVY;
       break;
    default:
       _weight=PANGO_WEIGHT_NORMAL;
       break;    
    }
    
    //
    // Variant:
    //
    PangoVariant _variant;
    switch(variant){ 
    case NORMAL_VARIANT:
      _variant=PANGO_VARIANT_NORMAL;
      break;
    case SMALLCAPS:
      _variant=PANGO_VARIANT_SMALL_CAPS;
      break;
    default:
       _variant=PANGO_VARIANT_NORMAL;
       break;
    }
    
    //
    // Stretch:
    // 
    PangoStretch _stretch;
    switch(stretch){
    case NORMAL_STRETCH:
      _stretch=PANGO_STRETCH_NORMAL;
      break;
    case ULTRACONDENSED:
      _stretch=PANGO_STRETCH_ULTRA_CONDENSED;
      break;
    case EXTRACONDENSED:
      _stretch=PANGO_STRETCH_EXTRA_CONDENSED;
      break;
    case CONDENSED:
      _stretch=PANGO_STRETCH_CONDENSED;
      break;
    case SEMICONDENSED:         
      _stretch=PANGO_STRETCH_SEMI_CONDENSED;
      break;
    case SEMIEXPANDED:
      _stretch=PANGO_STRETCH_SEMI_EXPANDED;
      break;
    case EXPANDED:
      _stretch=PANGO_STRETCH_EXPANDED;
      break;
    case EXTRAEXPANDED:
      _stretch=PANGO_STRETCH_EXTRA_EXPANDED;
      break;
    case ULTRAEXPANDED:
      _stretch=PANGO_STRETCH_ULTRA_EXPANDED;
      break;
    default:
       _stretch=PANGO_STRETCH_NORMAL;
       break;    
    }

    PangoFontDescription* font_description = pango_font_description_new();
    pango_font_description_set_family (font_description, family);
    pango_font_description_set_style (font_description, _style);
    pango_font_description_set_weight (font_description, _weight);
    pango_font_description_set_variant (font_description, _variant);
    pango_font_description_set_stretch (font_description, _stretch);
    pango_font_description_set_size(font_description, DRAWING_SCALE*PANGO_SCALE);
    pango_context_set_font_description (static_cast<PangoContext*>(*_pContextMgr), font_description);
  
}

void PostscriptDocument::for_each_glyph_do(const string& s, const GLYPH_FUNC func, void* contextData,
     bool applyOffset)
{

  PangoAttrList* const attrList = pango_attr_list_new(); // needed only for call to pango_itemize()

  GList* glItems = pango_itemize(
      pangoContext(), 
      s.c_str(), 
      0, s.length(),
      attrList,
      (PangoAttrIterator *) 0);

  pango_attr_list_unref(attrList);

  for (; glItems ; glItems = g_list_next(glItems)) {
    PangoItem* const pItem = reinterpret_cast<PangoItem*>(glItems->data);
    PangoGlyphString* const pGlyphString = pango_glyph_string_new();
    pango_shape(s.c_str() + pItem->offset, pItem->length, &pItem->analysis, pGlyphString);

    const FT_Face face = pango_ft2_font_get_face(pItem->analysis.font);
    PangoGlyphInfo* const pGlyphInfo = pGlyphString->glyphs;
    for (int i=0 ; i < pGlyphString->num_glyphs ; ++i) {
      const FT_UInt glyph_index = pGlyphInfo[i].glyph;    // get glyph index
            
      const PostscriptDocument::GlyphId glyphId(face, glyph_index); // construct GlyphId
      FreetypeGlyphMgr& glyphMgr = _glyphMap[glyphId];  // access glyph from map

      if (0 == static_cast<FT_Glyph>(glyphMgr)) { // if glyph is not in map
        //
        // access glyph from font face and put it in map
        //
        FT_Glyph glyph;
        //
        // DEBUG:
        //
        //std::cerr << "Glyph Index: " << glyph_index << std::endl;
        
        
        FT_Error error = FT_Load_Glyph(face,glyph_index,FT_LOAD_NO_BITMAP);
        if(error){
           //
           //DEBUG:
           //
           //std::cerr << "PANGO is returning a glyph index of " << std::hex << glyph_index << std::endl;
           //std::cerr << "but PANGO_GLYPH_UNKNOWN_FLAG is supposed to be: " << 0x10000000 << std::endl;
           //std::cerr << "and PANGO_GLYPH_EMPTY        is supposed to be: " << 0x0FFFFFFF << std::endl;
           
           //
           // Substitute something that works: All fonts are supposed
           // to handle glyph_index 0 as the default replacement glyph:
           //
           evalReturnCode(FT_Load_Glyph(face,0,FT_LOAD_NO_BITMAP),"FT_Load_Glyph"); 
        }else{
           evalReturnCode(FT_Load_Glyph(face, glyph_index,FT_LOAD_NO_BITMAP), "FT_Load_Glyph");
        }
        
        evalReturnCode(FT_Get_Glyph(face->glyph, &glyph), "FT_Get_Glyph");
        glyphMgr.assign(glyph);
      }
      ostream* pos = 0;
      double   x_rmove = 0, y_rmove = 0;
      if (applyOffset) {
        const PangoGlyphGeometry& geo = pGlyphInfo[i].geometry;
        if (geo.x_offset != 0 || geo.y_offset != 0) {
          const double scale = _fontSize / (DRAWING_SCALE * PANGO_SCALE);
          pos = reinterpret_cast<ostream*>(contextData);
          x_rmove = scale * geo.x_offset;
          y_rmove = scale * geo.y_offset;
          (*pos) << x_rmove << ' ' << y_rmove << " rmoveto" << endl;
        }
      }
      //
      // glyph is guaranteed to be in map:
      // Call the function that operates on the glyph:
      //
      (this->*func)(*_glyphMap.find(glyphId), contextData);
      if (applyOffset && pos) { // undo previous rmoveto
         (*pos) << -x_rmove << ' ' << -y_rmove << " rmoveto" << endl;
      }
    }
    pango_glyph_string_free(pGlyphString);
    pango_item_free(pItem);
  }
  g_list_free(glItems);
}


/** Add the next glyphs dimensions to the bounding box (contextData).
  * If the advance is in the x direction (the usual case),
  * the box grows in the x direction, yMax becomes the height
  * of the tallest character, and yMin the descent of the most
  * descending character.
  *
  * @param mapval std::pair<GlyphId, FreetypeGlyphMgr>
  * @param contextData std::pair<double, double>, the x and y dimensions
  */
void PostscriptDocument::accrue_dimensions(
    const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData)
{
  // const GlyphId& gid = static_cast<GlyphId>(mapval.first);
  const FreetypeGlyphMgr& glyphMgr = static_cast<FreetypeGlyphMgr>(mapval.second);
  const FT_Glyph& glyph = static_cast<FT_Glyph>(glyphMgr);
  //
  //
  //
  //const double y_adv = std::abs(glyph->advance.y / (double) 0x10000);
  //
  const double x_adv = std::abs(glyph->advance.x / (double) 0x10000); // convert from 16.16 format

  //
  //
  //
  FT_BBox bbox; 
  FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &bbox);

  //
  // Get the mins and maxes, converting from 26.6 format:
  //
  // const double x_min = bbox.xMin/64.0;
  // const double x_max = bbox.xMax/64.0;
  //
  const double y_min = bbox.yMin/64.0;
  const double y_max = bbox.yMax/64.0;
   
  StringDimensions *SD = reinterpret_cast<StringDimensions *>(contextData);

  SD->accrueXAdvance(x_adv);
  SD->setYMin(y_min);
  SD->setYMax(y_max);

}

/** Insert a Postscript glyph_routine call into output stream (contextData).
  */
void PostscriptDocument::invoke_glyph_routine(
    const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData)
{
  const GlyphId& gid = static_cast<GlyphId>(mapval.first);
  ostream* pos = reinterpret_cast<ostream*>(contextData);
  static_cast<ostream&>(*pos) << this->getFontSize() << " " << gid.str() << endl;
}

/** Returns the line spacing, x-advance, y-minimum and y-maximum
  * based on the current font face and font size.  A bounding box
  * around the text string, s, can be constructed from the xAdvance,
  * yMinimum, and yMaximum.  yMinimum tells you the descent from the
  * baseline. yMaximum tells you the ascent from the baseline.
  * The line spacing provides an inter-line spacing for multi-line 
  * text layout.
  *
  * This version accepts a const C-style character string.
  *
  */
void PostscriptDocument::get_dimensions(const char* s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax)
{

  StringDimensions SD;
  for_each_glyph_do(s, &PostscriptDocument::accrue_dimensions,&SD);

  const double scale = _fontSize / DRAWING_SCALE;
  //
  // We always want to retrieve at least the lineSpacing:
  //
  *lineSpacing = SD.getLineSpacing() * scale;
  //
  // But xAdvance, yMin, and yMax are only necessary to retrieve
  // if we want to have the bounding box, so we allow default
  // parameters set to NULL:
  //
  if(xAdvance!=NULL)  *xAdvance = SD.getXAdvance() * scale;
  if(yMin    !=NULL)  *yMin     = SD.getYMin()     * scale;
  if(yMax    !=NULL)  *yMax     = SD.getYMax()     * scale;
  
}

/** Returns the line spacing, x-advance, y-minimum and y-maximum
  * based on the current font face and font size.  A bounding box
  * around the text string, s, can be constructed from the xAdvance,
  * yMinimum, and yMaximum.  yMinimum tells you the descent from the
  * baseline. yMaximum tells you the ascent from the baseline.
  * The line spacing provides an inter-line spacing for multi-line 
  * text layout.
  *
  * This version accepts an STL standard string class string.
  *
  */
void PostscriptDocument::get_dimensions(std::string s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax){
	
	get_dimensions(s.c_str(),lineSpacing,xAdvance,yMin,yMax);
	
}


void show::apply(oPostscriptStream& os) const
{
  //cerr << "show::apply(os): _str = " << _str << endl;
  PostscriptDocument& doc = os.doc();
  doc.for_each_glyph_do(_str, &PostscriptDocument::invoke_glyph_routine, &os, true);
}

const unsigned int PostscriptDocument::DRAWING_SCALE = PANGO_SCALE;

/**
  * Writes out the document.
  *
  */
void PostscriptDocument::write(std::ostream& os, double llx, double lly, double urx, double ury)
{
  //
  // Output document header:
  //

  //
  // If any of the bounding box parameters are non-zero,
  // then write out an EPS document with a bounding box:
  //
  if(llx || lly || urx || ury){  
    //
    // Encapsulated PostScript Header:
    //
    os << "%!PS-Adobe-3.0 EPSF-3.0" << endl;
    os << "%%BoundingBox: " << int(llx) << " " << int(lly) << " " << int(urx) << " " << int(ury) << endl;
    os << "%%HiResBoundingBox: " << std::setprecision(9) << llx << " " << lly << " " << urx << " " << ury << endl;
  }else{
    //
    // Normal Postscript header for print media:
    //
    os << "%!PS-Adobe-3.0" << endl;  
  }
  //
  // Rest of header:
  //
  os << "%%Creator: libLASi C++ Stream Interface for Postscript. LASi is hosted on http://www.unifont.org." << endl;
  os << "%%Copyright: (c) 2003, 2004, 2006 by Larry Siden.  All Rights Reserved. Released under the LGPL." << endl;
  os << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START Document Header:" << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;

  //
  // If a "%!PS" is found at the beginning of the user's header,
  // warn them that LASi already provides that:
  //
  if( _osHeader.str().find("%!PS")!=string::npos) cerr << "WARNING: LASi automatically provides \"%!PS-Adobe-3.0\" at the start of the document!" << endl;

  //
  // Make sure there is a "%%BeginProlog" to complement the "%%EndProlog" that
  // LASi puts after the end of the glyph routines:
  // 
  if( _osHeader.str().find("%%BeginProlog")==string::npos) os << "%%BeginProlog" << endl;
  
  os << _osHeader.str() << endl;

  //
  // Write out the glyph routines:  
  //
    
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START LASi Glyph Routines:" << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%%BeginResource: GlyphRoutines" << endl;
  
  for_each(_glyphMap.begin(), _glyphMap.end(),
      write_glyph_routine_to_stream(os, static_cast<PangoContext*>(*_pContextMgr)));
 
  os << "%%EndResource" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% END LASi Glyph Routines:"   << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%%EndProlog" << endl;
  
  os << endl;
  
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START Document Body:"       << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;

  os << _osBody.str() << endl;

  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% END Document Body:"         << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START Document Footer:"     << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%%Trailer" << endl;
    
  os << _osFooter.str() << endl;
  
  os << "%%EOF" << endl;
  
}
