/****************************************************************************
 * Copyright (C) 2009-2015 EPAM Systems
 *
 * This file is part of Indigo toolkit.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#include "render_cdxml.h"

#include "render_params.h"
#include "base_cpp/output.h"
#include "molecule/molecule.h"
#include "molecule/molecule_cdxml_saver.h"

using namespace indigo;

// Molecule position on the page
struct Pos
{
   // Structure min and max coordinates
   Vec2f str_min, str_max;

   // Offset and size on the page
   Vec2f page_offset;
   Vec2f size, all_size;

   // Final offset for the coordinates
   Vec2f offset;
   float title_offset_y;

   // Structure scaling coefficient
   float scale;
};

void _getBounds (RenderParams& params, BaseMolecule &mol, Vec2f &min, Vec2f &max, float &scale)
{
   // Compute average bond length
   float avg_bond_length = 1;
   if (mol.edgeCount() > 0)
   {
      float bond_length_sum = 0;
      for (int i = mol.edgeBegin(); i != mol.edgeEnd(); i = mol.edgeNext(i))
      {
         const Edge& edge = mol.getEdge(i);
         const Vec3f &p1 = mol.getAtomXyz(edge.beg);
         const Vec3f &p2 = mol.getAtomXyz(edge.end);
         bond_length_sum += Vec3f::dist(p1, p2);
      }
      avg_bond_length = bond_length_sum / mol.edgeCount();
   }

   float bond_length = 1;
   if (params.cnvOpt.bondLength > 0)
      bond_length = params.cnvOpt.bondLength / 100.0f;

   scale = bond_length / avg_bond_length;

   for (int i = mol.vertexBegin(); i != mol.vertexEnd(); i = mol.vertexNext(i))
   {
      Vec3f &p = mol.getAtomXyz(i);
      Vec2f p2(p.x, p.y);

      if (i == mol.vertexBegin())
         min = max = p2;
      else
      {
         min.min(p2);
         max.max(p2);
      }
   }

   min.scale(scale);
   max.scale(scale);
}
int _findReverse(int from, int to, const Array<char>& _array, char value) {
   for (int i = to - 1; i >= from; i--) {
      if (_array[i] == value)
         return i;
   }
   return -1;
}
int _getLongestLineXml(const Array<char>& line) {
   int longest_line = 0;
   if (line.size() > 0)
   {
      int start = 0;
      while (start < line.size() - 1)
      {
         int next = line.find(start + 1, line.size(), '\n');
         if (next == -1)
            next = line.size() - 1;
         int st = _findReverse(start + 1, next - 1, line, '>');
         if (st == -1) {
            st = start;
         }

         longest_line = __max(next - st, longest_line);

         start = next;
      }
   }
   return longest_line;
}
int _getLongestLine(const Array<char>& line) {
   int longest_line = 0;
   if (line.size() > 0)
   {
      int start = 0;
      while (start < line.size())
      {
         int next = line.find(start + 1, line.size(), '\n');
         if (next == -1)
            next = line.size();

         longest_line = __max(next - start, longest_line);

         start = next;
      }
   }
   return longest_line;
}
void RenderParamCdxmlInterface::render (RenderParams& params)
{
   MoleculeCdxmlSaver saver(*params.rOpt.output);

   Array<BaseMolecule*> mols;
   if (params.mols.size() != 0)
      for (int i = 0; i < params.mols.size(); ++i)
         mols.push(params.mols[i]);
   else
      mols.push(params.mol.get());

   Vec2f offset(0, 0);
   Array<float> column_widths;
   column_widths.resize(params.cnvOpt.gridColumnNumber);
   column_widths.fill(0);

   Array<float> title_widths;
   title_widths.resize(mols.size());
   title_widths.fill(0);

   Array<float> key_widths;
   key_widths.resize(mols.size());
   key_widths.fill(0);

   Array<float> prop_widths;
   prop_widths.resize(mols.size());
   prop_widths.fill(0);

   Array<Pos> positions;
   positions.resize(mols.size());

   Array<float> title_heights;
   title_heights.resize(mols.size());
   title_heights.fill(0);

   for (int mol_idx = 0; mol_idx < mols.size(); ++mol_idx)
   {
      int column = mol_idx % params.cnvOpt.gridColumnNumber;

      Pos &p = positions[mol_idx];
      _getBounds(params, mols[mol_idx]->asMolecule(), p.str_min, p.str_max, p.scale);

      float width = p.str_max.x - p.str_min.x;

      // Check titles width
      if (mol_idx < params.titles.size())
      {
         const Array<char> &title = params.titles[mol_idx];
        
         
         if (title.size() > 0)
         {
            int longest_line = _getLongestLine(title);

            // On average letters has width 6
            float letter_width = params.rOpt.titleFontFactor / 1.5f;

            float title_width = longest_line * letter_width / MoleculeCdxmlSaver::BOND_LENGTH;
            title_widths[mol_idx] = title_width;
            width = __max(width, title_width);
         }
      }
      if (params.rOpt.cdxml_context.get() != NULL) {
         RenderCdxmlContext& context = params.rOpt.cdxml_context.ref();
         if (context.enabled) {
            RenderCdxmlContext::PropertyData& data = context.property_data.at(mol_idx);
            float letter_width = context.propertyFontSize / 1.5f;
            int longest_line = _getLongestLineXml(data.propertyName);
            
            key_widths[mol_idx] = longest_line * letter_width / MoleculeCdxmlSaver::BOND_LENGTH;

            longest_line += _getLongestLineXml(data.propertyValue);
            float prop_width = longest_line * letter_width / MoleculeCdxmlSaver::BOND_LENGTH;
            prop_widths[mol_idx] = prop_width;
            
            width = __max(width, prop_width);
         }
      }

      column_widths[column] = __max(width, column_widths[column]);
   }

   float x_margins_base = 1.1f, y_margins_base = 1.1f;
   float x_grid_base = 1.5f;

   Array<float> column_offset;
   column_offset.resize(params.cnvOpt.gridColumnNumber);
   column_offset[0] = params.cnvOpt.marginX / 10.0f + x_margins_base;
   for (int i = 1; i < params.cnvOpt.gridColumnNumber; i++)
      column_offset[i] = column_offset[i - 1] + column_widths[i - 1] + x_grid_base;

   float page_y_offset_base = params.cnvOpt.marginY / 10.0f + y_margins_base;
   float row_y_offset = page_y_offset_base;
   int last_row = 0;
   float max_y = 0;

   float title_y = 0;

   // Get each structure bounds 
   int row_moved = 0;
   for (int mol_idx = 0; mol_idx < mols.size(); ++mol_idx)
   {
      Pos &p = positions[mol_idx];

      int column = mol_idx % params.cnvOpt.gridColumnNumber;
      int row = mol_idx / params.cnvOpt.gridColumnNumber;

      p.page_offset.x = column_offset[column];
      p.page_offset.y = row_y_offset;

      p.size.diff(p.str_max, p.str_min);
      p.all_size = p.size;
      
      if (mol_idx < params.titles.size())
      {
         const Array<char> &title = params.titles[mol_idx];
         if (title.size() > 0)
         {
            int lines = title.count('\n') + 1;
            float letter_height = params.rOpt.titleFontFactor / MoleculeCdxmlSaver::BOND_LENGTH;
            //float title_height = lines * saver.textLineHeight();
            //p.all_size.y += title_height + saver.textLineHeight(); // Add blank line
            float title_height = lines * letter_height;
            title_heights[mol_idx] = title_height;
            p.all_size.y += title_height + letter_height; // Add blank line
         }
      }
      if (params.rOpt.cdxml_context.get() != NULL) {
         RenderCdxmlContext& context = params.rOpt.cdxml_context.ref();
         if (context.enabled) {
            RenderCdxmlContext::PropertyData& data = context.property_data.at(mol_idx);
            int lines = data.propertyName.count('\n') + 1;

            float letter_height = params.rOpt.titleFontFactor / MoleculeCdxmlSaver::BOND_LENGTH;
            float prop_height = lines * letter_height;
            p.all_size.y += prop_height + letter_height; // Add blank line
         }
      }

      // Check that the structure is fully on a single page
      int pbegin = (int)(p.page_offset.y / saver.pageHeight());
      int pend = (int)((p.page_offset.y + p.all_size.y) / saver.pageHeight());
      // Additional check that we didn't moved this row before
      if (pbegin != pend && row_moved != row)
      {
         // Update starting row_y_offset for the whole row and start this row again
         row_y_offset = (pbegin + 1) * saver.pageHeight() + page_y_offset_base;
         mol_idx = row * params.cnvOpt.gridColumnNumber - 1;
         row_moved = row;
         continue;
      }

      p.offset.x = p.page_offset.x - p.str_min.x + (column_widths[column] - p.size.x) / 2;
      p.offset.y = -p.page_offset.y - p.str_max.y;

      p.title_offset_y = -p.page_offset.y - p.size.y - 1.0f;

      max_y = __max(max_y, p.page_offset.y + p.all_size.y);

      int next_row = (mol_idx + 1) / params.cnvOpt.gridColumnNumber;
      if (last_row != next_row)
      {
         row_y_offset = max_y + 1.0f;
         last_row = next_row;
      }
   }

   if (params.cnvOpt.comment.size() > 0)
   {
      int lines = params.cnvOpt.comment.count('\n') + 1;
      float comment_height = lines * 0.3f;
      max_y += 0.3f;
      title_y = max_y;
      max_y += comment_height;
   }

   MoleculeCdxmlSaver::Bounds b;
   b.min.set(0, 0);

   float w = column_offset[params.cnvOpt.gridColumnNumber - 1] + column_widths[params.cnvOpt.gridColumnNumber - 1];
   w += x_margins_base + params.cnvOpt.marginX / 10.0f;

   b.max.set(w, max_y + y_margins_base);
   saver.beginDocument(&b);
   
   Array<char> font_attr;
   ArrayOutput font_out(font_attr);
   
   font_out.printf("<s size=\"%f\"", params.rOpt.titleFontFactor);

   if (params.rOpt.cdxml_context.get() != NULL) {
      RenderCdxmlContext& context = params.rOpt.cdxml_context.ref();
      if (context.fonttable.size() > 0) {
         saver.addFontTable(context.fonttable.ptr());
      }
      if (context.colortable.size() > 0) {
         saver.addColorTable(context.colortable.ptr());
      }
      if (context.titleFont.size() > 0) {
         font_out.printf(" font=\"%s\"", context.titleFont.ptr());
      }
      if (context.titleFace.size() > 0) {
         font_out.printf(" face=\"%s\"", context.titleFace.ptr());
      }
   }
   font_out.printf(">");
   font_attr.push(0);

   //if (params.rOtitleFont.size() > 0) {
   //   font_out.printf("id=\"5\" charset=\"iso-8859-1\" name=\"%s\"", params.cnvOpt.titleFont.ptr());
   //   font_attr.push(0);
   //   saver.addFontTable(font_attr.ptr());
   //   /*
   //   * Set font as id 5 always
   //   */

   //   font_attr.clear();
   //   if (params.rOpt.titleFontFactor > 1)
   //      font_out.printf(" font=\"5\" size=\"%.0f\"", params.rOpt.titleFontFactor);
   //   else
   //      font_out.printf(" font=\"5\"");
   //} else {
   //   if (params.rOpt.titleFontFactor > 1)
   //      font_out.printf(" size=\"%.0f\"", params.rOpt.titleFontFactor);
   //}
   //font_attr.push(0);
   saver.beginPage(&b);
   Array<char> title_font;

   for (int mol_idx = 0; mol_idx < mols.size(); ++mol_idx)
   {
      int column = mol_idx % params.cnvOpt.gridColumnNumber;

      Pos &p = positions[mol_idx];
      Vec2f offset = p.offset;
      offset.scale(1 / p.scale);
      saver.saveMoleculeFragment(mols[mol_idx]->asMolecule(), offset, p.scale);
      
      if (mol_idx < params.titles.size())
      {
         const Array<char> &title = params.titles[mol_idx];
         
         if (title.size() > 0)
         {
            title_font.clear();
            title_font.readString(font_attr.ptr(), false);
            // Get title bounding box
            float x = params.cnvOpt.titleAlign.getAnchorPoint(p.page_offset.x, column_widths[column], title_widths[mol_idx]);

            const char *alignment_str = "";
            MultilineTextLayout alignment = params.cnvOpt.titleAlign;
            if (alignment.inbox_alignment == MultilineTextLayout::Center)
               alignment_str = "Center";
            if (alignment.inbox_alignment == MultilineTextLayout::Left)
               alignment_str = "Left";
            if (alignment.inbox_alignment == MultilineTextLayout::Right)
               alignment_str = "Right";

            Vec2f title_offset(x, p.title_offset_y);
            title_font.appendString(title.ptr(), true);
            title_font.appendString("</s>", true);

            saver.addCustomText(title_offset, alignment_str, params.rOpt.titleFontFactor, title_font.ptr());
         }
      }
      if (params.rOpt.cdxml_context.get() != NULL) {
         RenderCdxmlContext& context = params.rOpt.cdxml_context.ref();
         if (context.enabled) {
            RenderCdxmlContext::PropertyData& data = context.property_data.at(mol_idx);
            float prop_width = prop_widths[mol_idx];
            float key_width = key_widths[mol_idx];
            float prop_offset_y = p.title_offset_y - title_heights[mol_idx];
            float x = params.cnvOpt.titleAlign.getAnchorPoint(p.page_offset.x, column_widths[column], prop_width);

            float prop_offset_key = prop_width * 0.5f;
            float prop_offset_val = prop_offset_key - (prop_width - key_width);
            if (context.keyAlignment == RenderCdxmlContext::ALIGNMENT_LEFT) {
               Vec2f title_offset_key(x - prop_offset_key, prop_offset_y);
               Vec2f title_offset_val(x + prop_offset_val, prop_offset_y);
               saver.addCustomText(title_offset_key, "Left", context.propertyFontSize, data.propertyName.ptr());
               saver.addCustomText(title_offset_val, "Left", context.propertyFontSize, data.propertyValue.ptr());
            }
            else {
               Vec2f title_offset_key(x + prop_offset_val, prop_offset_y);
               Vec2f title_offset_val(x + prop_offset_val, prop_offset_y);
               saver.addCustomText(title_offset_key, "Right", context.propertyFontSize, data.propertyName.ptr());
               saver.addCustomText(title_offset_val, "Left", context.propertyFontSize, data.propertyValue.ptr());
            }
         }

      }
      
     
   }

   if (params.cnvOpt.comment.size() > 0)
   {
      Vec2f pos(b.max.x / 2, -title_y);
      saver.addText(pos, params.cnvOpt.comment.ptr());
   }

   saver.endPage();
   saver.endDocument();
}
