
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2019 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 "dbCellGraphUtils.h"

#include <set>
#include <sstream>

#include <QBuffer>
#include <QResource>
#include <QFileInfo>
#include <QDomElement>
#include <QDomDocument>
#include <QXmlStreamWriter>
#include <QTextStream>
#if QT_VERSION >= 0x050000
#  include <QUrlQuery>
#endif

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::fromUtf8 (":/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 ());
    }

#if QT_VERSION >= 0x050000
    QList<QPair<QString, QString> > queryItems = QUrlQuery (url.query ()).queryItems ();
#else
    QList<QPair<QString, QString> > queryItems = url.queryItems ();
#endif
    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::fromUtf8 ("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::fromUtf8 ("www.klayout.org/layout-statistics-template");
  static QString template_cmd_if = QString::fromUtf8 ("if");
  static QString template_cmd_true = QString::fromUtf8 ("true");
  static QString template_cmd_false = QString::fromUtf8 ("false");
  static QString template_cmd_eval = QString::fromUtf8 ("eval");
  static QString template_cmd_query = QString::fromUtf8 ("query");
  static QString template_cmd_begin = QString::fromUtf8 ("begin");
  static QString template_cmd_end = QString::fromUtf8 ("end");
  static QString template_cmd_max = QString::fromUtf8 ("max");
  static QString template_cmd_each = QString::fromUtf8 ("each");
  static QString template_value_true = QString::fromUtf8 ("true");
  static QString template_value_empty_query = QString::fromUtf8 ("");
  static QString template_name_expr = QString::fromUtf8 ("expr");
  static QString template_name_max = QString::fromUtf8 ("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 ShapeStatistics
{
public:
  ShapeStatistics ()
  {
    // .. nothing yet ..
  }

  void compute (const db::Shapes &shapes)
  {
    for (db::Shapes::shape_iterator i = shapes.begin (db::ShapeIterator::All); ! i.at_end (); ++i) {
      size_t n = 1;
      if (i.in_array ()) {
        n = i.array ().array_size ();
        m_count [i.array ().type ()] += 1;
        i.finish_array ();
      }
      m_count [i->type ()] += n;
    }
  }

  void operator*= (size_t f)
  {
    for (std::map<db::Shape::object_type, size_t>::iterator c = m_count.begin (); c != m_count.end (); ++c) {
      c->second *= f;
    }
  }

  void operator+= (const ShapeStatistics &other)
  {
    for (std::map<db::Shape::object_type, size_t>::const_iterator c = other.m_count.begin (); c != other.m_count.end (); ++c) {
      m_count [c->first] += c->second;
    }
  }

  size_t count (db::Shape::object_type t) const
  {
    std::map<db::Shape::object_type, size_t>::const_iterator c = m_count.find (t);
    if (c != m_count.end ()) {
      return c->second;
    } else {
      return 0;
    }
  }

  size_t box_total () const
  {
    return count (db::Shape::Box) + count (db::Shape::BoxArrayMember) + count (db::Shape::ShortBox) + count (db::Shape::ShortBoxArrayMember);
  }

  size_t box_single () const
  {
    return count (db::Shape::Box) + count (db::Shape::ShortBox);
  }

  size_t box_array () const
  {
    return count (db::Shape::BoxArray) + count (db::Shape::ShortBoxArray);
  }

  size_t polygon_total () const
  {
    return count (db::Shape::Polygon) + count (db::Shape::PolygonRef) + count (db::Shape::PolygonPtrArrayMember)
         + count (db::Shape::SimplePolygon) + count (db::Shape::SimplePolygonRef) + count (db::Shape::SimplePolygonPtrArrayMember);
  }

  size_t polygon_single () const
  {
    return count (db::Shape::Polygon) + count (db::Shape::PolygonRef)
         + count (db::Shape::SimplePolygon) + count (db::Shape::SimplePolygonRef);
  }

  size_t polygon_array () const
  {
    return count (db::Shape::PolygonPtrArray) + count (db::Shape::SimplePolygonPtrArray);
  }

  size_t path_total () const
  {
    return count (db::Shape::Path) + count (db::Shape::PathRef) + count (db::Shape::PathPtrArrayMember);
  }

  size_t path_single () const
  {
    return count (db::Shape::Path) + count (db::Shape::PathRef);
  }

  size_t path_array () const
  {
    return count (db::Shape::PathPtrArray);
  }

  size_t text_total () const
  {
    return count (db::Shape::Text) + count (db::Shape::TextRef) + count (db::Shape::TextPtrArrayMember);
  }

  size_t text_single () const
  {
    return count (db::Shape::Text) + count (db::Shape::TextRef);
  }

  size_t text_array () const
  {
    return count (db::Shape::TextPtrArray);
  }

  size_t edge_total () const
  {
    return count (db::Shape::Edge);
  }

  size_t user_total () const
  {
    return count (db::Shape::UserObject);
  }

private:
  std::map<db::Shape::object_type, size_t> m_count;
};

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

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)
{
  static QString s_per_layer_stat_path_ld = QString::fromUtf8 ("per-layer-stat-ld");
  static QString s_per_layer_stat_path_name = QString::fromUtf8 ("per-layer-stat-name");

  QUrl qurl (tl::to_qstring (url));
  QFileInfo fi (qurl.path ());

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

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

  } else if (fi.baseName () == s_per_layer_stat_path_ld || fi.baseName () == s_per_layer_stat_path_name) {

    //  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"));

    std::vector <unsigned int> layers;
    for (unsigned int i = 0; i < layout.layers (); ++i) {
      if (layout.is_valid_layer (i)) {
        layers.push_back (i);
      }
    }

    if (fi.baseName () == s_per_layer_stat_path_ld) {
      std::sort (layers.begin (), layers.end (), CompareLDName (layout));
    } else {
      std::sort (layers.begin (), layers.end (), CompareNameLD (layout));
    }

    os << "<html>" << std::endl
       <<   "<body>" << std::endl
       <<     "<h2>" << tl::to_string (QObject::tr ("Detailed Layer Statistics for '")) << m_h->name () << "'</h2>" << std::endl

       <<     "<p>" << std::endl
       <<     "<table cellspacing=\"5\" cellpadding=\"5\">" << std::endl

       <<       "<tr>" << std::endl
       <<         "<th bgcolor=\"#f0f0f0\">" << tl::to_string (QObject::tr ("Layer")) << "</th>" << std::endl
       <<         "<th colspan=\"3\" bgcolor=\"#f0f0f0\">" << tl::to_string (QObject::tr ("Boxes")) << "</th>" << std::endl
       <<         "<th colspan=\"3\" bgcolor=\"#f0f0f0\">" << tl::to_string (QObject::tr ("Polygons")) << "</th>" << std::endl
       <<         "<th colspan=\"3\" bgcolor=\"#f0f0f0\">" << tl::to_string (QObject::tr ("Paths")) << "</th>" << std::endl
       <<         "<th colspan=\"3\" bgcolor=\"#f0f0f0\">" << tl::to_string (QObject::tr ("Texts")) << "</th>" << std::endl
       <<         "<th bgcolor=\"#f0f0f0\">" << tl::to_string (QObject::tr ("Edges")) << "</th>" << std::endl
       <<         "<th bgcolor=\"#f0f0f0\">" << tl::to_string (QObject::tr ("User objects")) << "</th>" << std::endl
       <<       "</tr>" << std::endl

       <<       "<tr>" << std::endl
       <<         "<th></th>" << std::endl
       <<         "<th>" << tl::to_string (QObject::tr ("(total)")) << "</th><th>" << tl::to_string (QObject::tr ("(single)")) << "</th><th>" << tl::to_string (QObject::tr ("(arrays)")) << "</th>" << std::endl
       <<         "<th>" << tl::to_string (QObject::tr ("(total)")) << "</th><th>" << tl::to_string (QObject::tr ("(single)")) << "</th><th>" << tl::to_string (QObject::tr ("(arrays)")) << "</th>" << std::endl
       <<         "<th>" << tl::to_string (QObject::tr ("(total)")) << "</th><th>" << tl::to_string (QObject::tr ("(single)")) << "</th><th>" << tl::to_string (QObject::tr ("(arrays)")) << "</th>" << std::endl
       <<         "<th>" << tl::to_string (QObject::tr ("(total)")) << "</th><th>" << tl::to_string (QObject::tr ("(single)")) << "</th><th>" << tl::to_string (QObject::tr ("(arrays)")) << "</th>" << std::endl
       <<         "<th>" << tl::to_string (QObject::tr ("(total)")) << "</th>" << std::endl
       <<         "<th>" << tl::to_string (QObject::tr ("(total)")) << "</th>" << std::endl
       <<       "</tr>" << std::endl
      ;

    db::CellCounter cc (&layout);

    tl::RelativeProgress progress (tl::to_string (QObject::tr ("Collecting statistics")), layers.size () * layout.cells (), 100000);
    for (std::vector <unsigned int>::const_iterator l = layers.begin (); l != layers.end (); ++l) {

      ShapeStatistics st_hier;
      ShapeStatistics st_flat;

      for (db::Layout::top_down_const_iterator c = layout.begin_top_down (); c != layout.end_top_down (); ++c) {

        ShapeStatistics st;
        st.compute (layout.cell (*c).shapes (*l));

        st_hier += st;
        st *= cc.weight (*c);
        st_flat += st;

        ++progress;

      }

      os <<       "<tr>" << std::endl
         <<         "<td>" << layout.get_properties (*l).to_string () << "</td>" << std::endl
         // Boxes (total, single, array)
         <<         "<td>" << st_hier.box_total () << "<br></br>" << st_flat.box_total () << "</td>" << std::endl
         <<         "<td>" << st_hier.box_single () << "<br></br>" << st_flat.box_single () << "</td>" << std::endl
         <<         "<td>" << st_hier.box_array () << "<br></br>" << st_flat.box_array () << "</td>" << std::endl
         // Polygons (total, single, array)
         <<         "<td>" << st_hier.polygon_total () << "<br></br>" << st_flat.polygon_total () << "</td>" << std::endl
         <<         "<td>" << st_hier.polygon_single () << "<br></br>" << st_flat.polygon_single () << "</td>" << std::endl
         <<         "<td>" << st_hier.polygon_array () << "<br></br>" << st_flat.polygon_array () << "</td>" << std::endl
         // Paths (total, single, array)
         <<         "<td>" << st_hier.path_total () << "<br></br>" << st_flat.path_total () << "</td>" << std::endl
         <<         "<td>" << st_hier.path_single () << "<br></br>" << st_flat.path_single () << "</td>" << std::endl
         <<         "<td>" << st_hier.path_array () << "<br></br>" << st_flat.path_array () << "</td>" << std::endl
         // Texts (total, single, array)
         <<         "<td>" << st_hier.text_total () << "<br></br>" << st_flat.text_total () << "</td>" << std::endl
         <<         "<td>" << st_hier.text_single () << "<br></br>" << st_flat.text_single () << "</td>" << std::endl
         <<         "<td>" << st_hier.text_array () << "<br></br>" << st_flat.text_array () << "</td>" << std::endl
         // Edges (total)
         <<         "<td>" << st_hier.edge_total () << "<br></br>" << st_flat.edge_total () << "</td>" << std::endl
         // User objects (total)
         <<         "<td>" << st_hier.user_total () << "<br></br>" << st_flat.user_total () << "</td>" << std::endl
         // ...
         <<         "<td>" << tl::to_string (QObject::tr ("(hier)")) << "<br></br>" << tl::to_string (QObject::tr ("(flat)")) << "</td>" << std::endl
         <<       "</tr>" << std::endl
        ;

    }

    os <<     "</table>" << std::endl
       <<     "</p>" << std::endl
       <<     tl::to_string (QObject::tr ("<h4>Note</h4>"
                                          "<p>"
                                          "\"(hier)\" is the object count where each cell counts once. "
                                          "\"(flat)\" is the \"as if flat\" count where the cells count as many times as they are seen from the top cells."
                                          "</p>"
                                          "<p>"
                                          "\"(total)\" is the effective number of shapes. \"(single)\" are the single shapes. "
                                          "\"(arrays)\" is the number of shape arrays where each array counts as one, but contributes many individual shapes to \"(total)\"."
                                          "</p>"
                                         ))
       <<   "</body>" << std::endl
       << "</html>";

    return os.str ();

  } 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
       <<     "<p>" << 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 (db::Layout::meta_info_iterator meta = layout.begin_meta (); meta != layout.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;
    os <<     "</p>" << std::endl;

    std::vector <unsigned int> layers_with_oasis_names;

    std::vector <unsigned int> layers_sorted_by_ld;
    layers_sorted_by_ld.reserve (num_layers);
    for (unsigned int i = 0; i < layout.layers (); ++i) {
      if (layout.is_valid_layer (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));
     
    if (! layers_sorted_by_ld.empty ()) {

      os <<     "<h2>" << tl::to_string (QObject::tr ("Layers (sorted by layer and datatype)")) << "</h2>" << std::endl
         <<     "<p><a href=\"" << tl::to_string (s_per_layer_stat_path_ld) << "\">Detailed layer statistics</a></p>" << std::endl
         <<     "<p>" << 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 ("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;
      os <<     "</p>" << std::endl;

    }

    if (! layers_with_oasis_names.empty ()) {

      os <<     "<h2>" << tl::to_string (QObject::tr ("Layers (sorted by layer names)")) << "</h2>" << std::endl
         <<     "<p><a href=\"" << tl::to_string (s_per_layer_stat_path_name) << "\">Detailed layer statistics</a></p>" << std::endl
         <<     "<p>" << std::endl
         <<     "<table>" << std::endl
         <<     "<tr><td><b>" << tl::to_string (QObject::tr ("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 <<     "</p>" << std::endl;

    }

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

    return os.str ();

  }
}

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

LayoutStatisticsForm::LayoutStatisticsForm (QWidget *parent, lay::LayoutView *view, const char *name)
  : QDialog (parent), Ui::LayoutStatisticsForm (), mp_source (0)
{
  setObjectName (QString::fromUtf8 (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 ();
}

}



