
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2016 Matthias Koefferlein

  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, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/


#include "layLayoutStatisticsForm.h"
#include "layLayoutView.h"
#include "tlInternational.h"
#include "tlString.h"
#include "tlExpression.h"
#include "tlTimer.h"
#include "dbLayoutQuery.h"

#include <set>
#include <sstream>

#include <QtCore/QBuffer>
#include <QtCore/QResource>
#include <QtCore/QFileInfo>
#include <QtXml/QDomElement>
#include <QtXml/QDomDocument>
#include <QtXml/QXmlStreamWriter>

namespace lay
{

/**
 *  @brief A comparison operator to sort layers by layer and datatype and then by name
 */
struct CompareLDName 
{
  CompareLDName (const db::Layout &l) 
    : mp_layout (&l)
  {
    //  .. nothing yet ..
  }

  bool operator() (unsigned int a, unsigned int b)
  {
    if (mp_layout->is_valid_layer (a) && mp_layout->is_valid_layer (b)) {
      const db::LayerProperties &lp_a = mp_layout->get_properties (a);
      const db::LayerProperties &lp_b = mp_layout->get_properties (b);
      if (lp_a.layer != lp_b.layer) {
        return lp_a.layer < lp_b.layer;
      }
      if (lp_a.datatype != lp_b.datatype) {
        return lp_a.datatype < lp_b.datatype;
      }
      if (lp_a.name != lp_b.name) {
        return lp_a.name < lp_b.name;
      }
    }
    return false;
  }

private:
  const db::Layout *mp_layout;
};

/**
 *  @brief A comparison operator to sort layers by name and then by layer and datatype 
 */
struct CompareNameLD 
{
  CompareNameLD (const db::Layout &l) 
    : mp_layout (&l)
  {
    //  .. nothing yet ..
  }

  bool operator() (unsigned int a, unsigned int b)
  {
    if (mp_layout->is_valid_layer (a) && mp_layout->is_valid_layer (b)) {
      const db::LayerProperties &lp_a = mp_layout->get_properties (a);
      const db::LayerProperties &lp_b = mp_layout->get_properties (b);
      if (lp_a.name != lp_b.name) {
        return lp_a.name < lp_b.name;
      }
      if (lp_a.layer != lp_b.layer) {
        return lp_a.layer < lp_b.layer;
      }
      if (lp_a.datatype != lp_b.datatype) {
        return lp_a.datatype < lp_b.datatype;
      }
    }
    return false;
  }

private:
  const db::Layout *mp_layout;
};

static std::string format_tech_name (const std::string &s)
{
  if (s.empty ()) {
    return s;
  } else {
    return " ('" + s + "')";
  }
}

// ------------------------------------------------------------

/**
 *  @brief A template processor for creating HTML pages from a template
 *
 *  TODO: this is just a first step and far from being complete.
 *  The template processor is used from the browser page by using an extension .stxml.
 *  It reads a XML file from a resource path ":/st/<path>" and converts it into HTML.
 */
class StatisticsTemplateProcessor 
{
public:
  StatisticsTemplateProcessor (const QUrl &url, const db::Layout *layout)
    : mp_layout (layout)
  {
    QResource res (QString::fromAscii (":/st/") + url.path ());
    if (res.isCompressed ()) {
      m_temp = qUncompress ((const unsigned char *)res.data (), (int)res.size ());
    } else {
      m_temp = QByteArray ((const char *)res.data (), (int)res.size ());
    }

    QList<QPair<QString, QString> > queryItems = url.queryItems ();
    for (QList<QPair<QString, QString> >::const_iterator q = queryItems.begin (); q != queryItems.end (); ++q) {
      m_top_eval.set_var (tl::to_string (q->first), tl::to_string (q->second));
    }
  }

  bool process ();

  const QByteArray &get () const
  {
    return m_output.buffer ();
  }

private:
  QByteArray m_temp;
  QBuffer m_output;
  tl::Eval m_top_eval;
  const db::Layout *mp_layout;

  void process (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer);
  void process_child_nodes (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer);
};

bool 
StatisticsTemplateProcessor::process ()
{
  m_output.open (QIODevice::WriteOnly);

  bool ret_value = false;

  try
  {
    tl::SelfTimer timer (tl::verbosity () > 21, "StatisticsForm: create content");

    QDomDocument doc;
    doc.setContent (m_temp, true);

    QXmlStreamWriter writer (&m_output);
    writer.writeStartDocument (QString::fromAscii ("1.0"));
    process (doc.documentElement (), m_top_eval, writer);
    writer.writeEndDocument ();

    ret_value = true;

  } catch (tl::Exception &ex) {
    tl::error << ex.msg ();
    QTextStream writer (&m_output);
    writer << tl::to_string (QObject::tr ("ERROR: evaluating template: ")).c_str () << ex.msg ().c_str ();
  }

  m_output.close ();
  return ret_value;
}

void
StatisticsTemplateProcessor::process_child_nodes (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer)
{
  if (element.isNull ()) {
    return;
  }

  for (QDomNode n = element.firstChild (); ! n.isNull (); n = n.nextSibling ()) {

    if (n.isElement ()) {

      process (n.toElement (), eval, writer);

    } else if (n.isCDATASection ()) {

      writer.writeCDATA (tl::to_qstring (eval.interpolate (tl::to_string (n.toCDATASection ().data ()))));

    } else if (n.isCharacterData ()) {

      QString t; 
      QTextStream s (&t);

      while (true) {

        s << n.toCharacterData ().data ();
        QDomNode nn = n.nextSibling ();
        if (nn.isNull () || ! nn.isCharacterData ()) {
          break;
        }
        n = nn;

      }

      writer.writeCharacters (tl::to_qstring (eval.interpolate (tl::to_string (t))));

    }

  }
}

void
StatisticsTemplateProcessor::process (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer)
{
  static QString template_namespace_uri = QString::fromAscii ("www.klayout.de/layout-statistics-template");
  static QString template_cmd_if = QString::fromAscii ("if");
  static QString template_cmd_true = QString::fromAscii ("true");
  static QString template_cmd_false = QString::fromAscii ("false");
  static QString template_cmd_eval = QString::fromAscii ("eval");
  static QString template_cmd_query = QString::fromAscii ("query");
  static QString template_cmd_begin = QString::fromAscii ("begin");
  static QString template_cmd_end = QString::fromAscii ("end");
  static QString template_cmd_max = QString::fromAscii ("max");
  static QString template_cmd_each = QString::fromAscii ("each");
  static QString template_value_true = QString::fromAscii ("true");
  static QString template_value_empty_query = QString::fromAscii ("");
  static QString template_name_expr = QString::fromAscii ("expr");
  static QString template_name_max = QString::fromAscii ("max");

  if (element.namespaceURI () == template_namespace_uri) {

    if (element.localName () == template_cmd_eval) {

      tl::Expression expr;
      eval.parse (expr, tl::to_string (element.attribute (template_name_expr, template_value_true)));
      expr.execute ();

    } else if (element.localName () == template_cmd_if) {

      QDomElement true_node, false_node;

      for (QDomNode n = element.firstChild (); ! n.isNull (); n = n.nextSibling ()) {
        QDomElement e = n.toElement ();
        if (! e.isNull () && e.namespaceURI () == template_namespace_uri) {
          if (e.localName () == template_cmd_true) {
            true_node = e;
          } else if (e.localName () == template_cmd_false) {
            false_node = e;
          }
        }
      }

      if (true_node.isNull () && false_node.isNull ()) {
        true_node = element;
      }

      tl::Expression expr;
      eval.parse (expr, tl::to_string (element.attribute (template_name_expr, template_value_true)));
      tl::Variant value = expr.execute ();

      if (value.to_bool ()) {
        if (! true_node.isNull ()) {
          process (true_node, eval, writer);
        } 
      } else {
        if (! false_node.isNull ()) {
          process (false_node, eval, writer);
        } 
      }

    } else if (element.localName () == template_cmd_query) {

      QDomElement begin_node, end_node, each_node, max_node;

      for (QDomNode n = element.firstChild (); ! n.isNull (); n = n.nextSibling ()) {
        QDomElement e = n.toElement ();
        if (! e.isNull () && e.namespaceURI () == template_namespace_uri) {
          if (e.localName () == template_cmd_begin) {
            begin_node = e;
          } else if (e.localName () == template_cmd_end) {
            end_node = e;
          } else if (e.localName () == template_cmd_max) {
            max_node = e;
          } else if (e.localName () == template_cmd_each) {
            each_node = e;
          }
        }
      }

      if (begin_node.isNull () && end_node.isNull () && max_node.isNull () && each_node.isNull ()) {
        each_node = element;
      }

      unsigned long max_count = std::numeric_limits<unsigned long>::max ();
      QString max_attr = element.attribute (template_name_max, QString ());
      if (! max_attr.isNull ()) {
        tl::Expression expr;
        eval.parse (expr, tl::to_string (max_attr));
        tl::Variant max = expr.execute ();
        if (max.can_convert_to_ulong ()) {
          max_count = max.to_ulong ();
        }
      }

      db::LayoutQuery q (tl::to_string (element.attribute (template_name_expr, template_value_empty_query)));

      db::LayoutQueryIterator qi (q, mp_layout, &eval);

      process_child_nodes (begin_node, qi.eval (), writer);

      for ( ; !qi.at_end (); ++qi) {
        if (max_count == 0) {
          process_child_nodes (max_node, qi.eval (), writer);
          break;
        } else {
          --max_count;
          process_child_nodes (each_node, qi.eval (), writer);
        }
      }

      process_child_nodes (end_node, qi.eval (), writer);

    }

  } else {

    writer.writeStartElement (element.nodeName ());

    if (element.hasAttributes ()) {
      //  Hint: attribute nodes are not children of the elements ..
      QDomNamedNodeMap attributes = element.attributes ();
      for (int i = 0; i < attributes.count (); ++i) {
        QDomAttr a = attributes.item (i).toAttr ();
        if (! a.isNull ()) {
          writer.writeAttribute (a.nodeName (), tl::to_qstring (eval.interpolate (tl::to_string (a.value ()))));
        }
      }
    }

    process_child_nodes (element, eval, writer);

    writer.writeEndElement ();

  }
}

// ------------------------------------------------------------

class StatisticsSource 
  : public lay::BrowserSource
{
public:
  StatisticsSource (const lay::LayoutHandleRef &h)
    : m_h (h)
  {
    //  .. nothing yet ..
  }

  std::string get (const std::string &url);

private:
  const lay::LayoutHandleRef m_h;
};

std::string
StatisticsSource::get (const std::string &url)
{
  QUrl qurl (tl::to_qstring (url));
  QFileInfo fi (qurl.path ());

  if (fi.suffix () == QString::fromAscii ("stxml")) {

    StatisticsTemplateProcessor tp (qurl, &m_h->layout ());
    tp.process ();
    std::string r = tp.get ().constData ();
    return r;

  } else {

    //  This is the default top level page
    //  TODO: handle other input as well

    const db::Layout &layout = m_h->layout ();

    std::ostringstream os;
    os.imbue (std::locale ("C"));

    size_t num_cells = layout.cells ();

    size_t num_layers = 0;
    for (unsigned int i = 0; i < layout.layers (); ++i) {
      if (layout.is_valid_layer (i)) {
        ++num_layers;
      }
    }

    os << "<html>" << std::endl
       <<   "<body>" << std::endl
       <<     "<h2>" << tl::to_string (QObject::tr ("Common Statistics For '")) << m_h->name () << "'</h2>" << std::endl
       <<     "<table>" << std::endl
       <<       "<tr>"
       <<         "<td>" << tl::to_string (QObject::tr ("Path")) << ":&nbsp;</td><td>" << m_h->filename () << "</td>"
       <<       "</tr>" << std::endl
       <<       "<tr>" 
       <<         "<td>" << tl::to_string (QObject::tr ("Technology")) << ":&nbsp;</td><td>" << m_h->technology ()->description () << format_tech_name (m_h->tech_name ()) << "</td>"
       <<       "</tr>" << std::endl
       <<       "<tr>"
       <<         "<td>" << tl::to_string (QObject::tr ("Database unit")) << ":&nbsp;</td><td>" << tl::sprintf ("%.12g ", layout.dbu ()) << tl::to_string (QObject::tr ("micron")) << "</td>"
       <<       "</tr>" << std::endl
       <<       "<tr>"
       <<         "<td>" << tl::to_string (QObject::tr ("Number of cells")) << ":&nbsp;</td><td>" << num_cells << "</td>"
       <<       "</tr>" << std::endl
       <<       "<tr>"
       <<         "<td>" << tl::to_string (QObject::tr ("Number of layers")) << ":&nbsp;</td><td>" << num_layers << "</td>"
       <<       "</tr>" << std::endl;
    for (lay::LayoutHandle::meta_info_iterator meta = m_h->begin_meta (); meta != m_h->end_meta (); ++meta) {
      os <<     "<tr><td>" << meta->description << "</td><td>" << meta->value << "</td></tr>" << std::endl;
    }
    os <<     "</table>" << std::endl
       <<     "<h2>" << tl::to_string (QObject::tr ("Top Cells")) << "</h2>" << std::endl
       <<     "<table>" << std::endl;
    for (db::Layout::top_down_const_iterator tc = layout.begin_top_down (); tc != layout.end_top_cells (); ++tc) {
      os <<     "<tr><td>" << layout.cell_name (*tc) << "</td></tr>" << std::endl;
    }
    os <<     "</table>" << std::endl;

    std::vector <unsigned int> layers_with_oasis_names;

    std::vector <unsigned int> layers_sorted_by_ld;
    layers_sorted_by_ld.reserve (layout.layers ());
    for (unsigned int i = 0; i < layout.layers (); ++i) {
      layers_sorted_by_ld.push_back (i);
      const db::LayerProperties &lp = layout.get_properties (i);
      if (! lp.name.empty ()) {
        layers_with_oasis_names.push_back (i);
      }
    }

    std::sort (layers_sorted_by_ld.begin (), layers_sorted_by_ld.end (), CompareLDName (layout));
    std::sort (layers_with_oasis_names.begin (), layers_with_oasis_names.end (), CompareNameLD (layout));
     
    os <<     "<h2>" << tl::to_string (QObject::tr ("Layers (sorted by layer and datatype)")) << "</h2>" << std::endl
       <<     "<table>" << std::endl
       <<     "<tr><td><b>" << tl::to_string (QObject::tr ("Layer/Datatype")) << "</b>&nbsp;&nbsp;</td>";
    if (! layers_with_oasis_names.empty ()) {
      os << "<td><b>" << tl::to_string (QObject::tr ("OASIS layer name")) << "</b></td>";
    }
    os << "</tr>" << std::endl;

    for (std::vector <unsigned int>::const_iterator i = layers_sorted_by_ld.begin (); i != layers_sorted_by_ld.end (); ++i) {
      if (layout.is_valid_layer (*i)) {
        const db::LayerProperties &lp = layout.get_properties (*i);
        os << "<tr>"
           <<   "<td>" << tl::sprintf ("%d/%d", lp.layer, lp.datatype) << "</td>";
        if (! layers_with_oasis_names.empty ()) {
          os <<   "<td>" << lp.name << "</td>";
        }
        os << "</tr>" << std::endl;
      }
    }

    os <<     "</table>" << std::endl;

    if (! layers_with_oasis_names.empty ()) {

      os <<     "<h2>" << tl::to_string (QObject::tr ("Layers (sorted by OASIS layer names)")) << "</h2>" << std::endl
         <<     "<table>" << std::endl
         <<     "<tr><td><b>" << tl::to_string (QObject::tr ("OASIS layer name")) << "</b>&nbsp;&nbsp;</td><td><b>" << tl::to_string (QObject::tr ("Layer/Datatype")) << "</b></td></tr>" << std::endl;
      
      for (std::vector <unsigned int>::const_iterator i = layers_with_oasis_names.begin (); i != layers_with_oasis_names.end (); ++i) {
        if (layout.is_valid_layer (*i)) {
          const db::LayerProperties &lp = layout.get_properties (*i);
          if (! lp.name.empty ()) {
            os << "<tr>"
               <<   "<td>" << lp.name << "</td>"
               <<   "<td>" << tl::sprintf ("%d/%d", lp.layer, lp.datatype) << "</td>"
               << "</tr>" << std::endl;
          }
        }
      }

      os <<     "</table>" << std::endl;

    }

    os <<   "</body>" << std::endl
       << "</html>" << std::endl;
       ;

    return os.str ();

  }
}

// ------------------------------------------------------------

LayoutStatisticsForm::LayoutStatisticsForm (QWidget *parent, lay::LayoutView *view, const char *name, Qt::WFlags fl)
  : QDialog (parent, fl), Ui::LayoutStatisticsForm (), mp_source (0)
{
  setObjectName (QString::fromAscii (name));

  Ui::LayoutStatisticsForm::setupUi (this);

  //  collect the distinct layout handles 
  std::set <lay::LayoutHandle *> handles;
  for (unsigned int n = 0; n < view->cellviews (); ++n) {
    handles.insert (view->cellview (n).operator-> ());
  }
  
  m_handles.reserve (handles.size ());
  for (unsigned int n = 0; n < view->cellviews (); ++n) {
    lay::LayoutHandle *h = view->cellview (n).operator-> ();
    if (handles.find (h) != handles.end ()) {
      m_handles.push_back (h);
      handles.erase (h);
      layout_cbx->addItem (tl::to_qstring (h->name ()));
    }
  }

  layout_cbx->setCurrentIndex (view->active_cellview_index ());

  connect (layout_cbx, SIGNAL (activated (int)), this, SLOT (layout_selected (int)));

  layout_selected (layout_cbx->currentIndex ());
}

LayoutStatisticsForm::~LayoutStatisticsForm ()
{
  browser->set_source (0);
  if (mp_source != 0) {
    delete mp_source;
    mp_source = 0;
  }
}

void
LayoutStatisticsForm::layout_selected (int index)
{
  if (index >= int (m_handles.size ()) || index < 0) {
    return;
  }

  browser->set_source (0);
  if (mp_source != 0) {
    delete mp_source;
  }

  mp_source = new StatisticsSource (m_handles [index]);
  browser->set_source (mp_source);
  browser->set_home ("int:index");
  browser->home ();
}

}



