
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2018 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 <math.h>

#include "dbBox.h"
#include "dbLayout.h"

#include "edtDialogs.h"
#include "layObjectInstPath.h"
#include "layCellView.h"
#include "layLayoutView.h"
#include "layMarker.h"
#include "tlException.h"
#include "tlExceptions.h"

namespace edt {

// ----------------------------------------------------------------------
//  edt::InstantiationForm implementation

InstantiationForm::InstantiationForm (QWidget *parent)
  : QDialog (parent), mp_view (0), mp_path (0), mp_marker (0), m_enable_cb_callbacks (false)
{
  setObjectName (QString::fromUtf8 ("instantiation_form"));

  Ui::InstantiationForm::setupUi (this);

  connect (list, SIGNAL (itemDoubleClicked (QListWidgetItem *)), this, SLOT (double_clicked (QListWidgetItem *)));
  connect (dbu_cb, SIGNAL (toggled (bool)), this, SLOT (display_mode_changed (bool)));
  connect (abs_cb, SIGNAL (toggled (bool)), this, SLOT (display_mode_changed (bool)));
}

InstantiationForm::~InstantiationForm ()
{
  if (mp_marker) {
    delete mp_marker;
    mp_marker = 0;
  }
}

void 
InstantiationForm::display_mode_changed (bool)
{
  if (! m_enable_cb_callbacks) {
    return;
  }

  mp_view->dbu_coordinates (dbu_cb->isChecked ());
  mp_view->absolute_coordinates (abs_cb->isChecked ());

  update ();
}

void 
InstantiationForm::double_clicked (QListWidgetItem *item)
{
  int row = list->row (item);
  if (row < 0) {
    return;
  }

  lay::CellView::unspecific_cell_path_type path (mp_view->cellview (mp_path->cv_index ()).combined_unspecific_path ());
  int nrow = 0;
  for (lay::ObjectInstPath::iterator p = mp_path->begin (); p != mp_path->end () && nrow < row; ++p, ++nrow) {
    path.push_back (p->inst_ptr.cell_index ());
  }

  mp_view->set_current_cell_path (mp_path->cv_index (), path);

  if (! mp_marker) {
    mp_marker = new lay::Marker (mp_view, mp_path->cv_index ());
  }

  const db::Layout &layout = mp_view->cellview (mp_path->cv_index ())->layout ();
  db::Box box = layout.cell (row == 0 ? mp_path->topcell () : path.back ()).bbox ();

  //  TODO: this does not consider global transformation and variants of this
  db::ICplxTrans abs_trans;
  nrow = 0;
  for (lay::ObjectInstPath::iterator p = mp_path->begin (); p != mp_path->end () && nrow < row; ++p, ++nrow) {
    abs_trans = abs_trans * (p->inst_ptr.cell_inst ().complex_trans (*(p->array_inst)));
  }

  mp_marker->set (box, abs_trans, mp_view->cv_transform_variants (mp_path->cv_index ()));
}

void 
InstantiationForm::show (lay::LayoutView *view, const lay::ObjectInstPath &path)
{
  mp_view = view;
  mp_path = &path;

  m_enable_cb_callbacks = false;
  dbu_cb->setChecked (mp_view->dbu_coordinates ());
  abs_cb->setChecked (mp_view->absolute_coordinates ());
  m_enable_cb_callbacks = true;

  update ();
  exec ();

  mp_view = 0;
  mp_path = 0;
}

void 
InstantiationForm::update ()
{
  bool dbu_coord = dbu_cb->isChecked ();
  bool abs_coord = abs_cb->isChecked ();

  const lay::CellView &cv = mp_view->cellview (mp_path->cv_index ());
  double dbu = cv->layout ().dbu ();

  layout_le->setText (tl::to_qstring (cv->name ()));

  list->clear ();

  list->addItem (tl::to_qstring (cv->layout ().cell_name (cv.ctx_cell_index ())));
  db::CplxTrans abs_trans;

  //  first include the context path of the cellview in order to tell the path within the cell shown
  for (lay::CellView::specific_cell_path_type::const_iterator p = cv.specific_path ().begin (); p != cv.specific_path ().end (); ++p) {

    //  build the instance information from the path

    db::CplxTrans trans (p->inst_ptr.cell_inst ().complex_trans (*(p->array_inst)));
    abs_trans = abs_trans * trans;

    if (abs_coord) {
      trans = abs_trans;
    }

    std::string line;
    line += cv->layout ().cell_name (p->inst_ptr.cell_index ());
    line += "\tat ";
    line += trans.to_string (true /*lazy*/, dbu_coord ? 0.0 : dbu);
    
    list->addItem (tl::to_qstring (line));

  }

  //  then, add the actual path to the object within the target cell
  for (lay::ObjectInstPath::iterator p = mp_path->begin (); p != mp_path->end (); ++p) {

    //  build the instance information from the path

    db::CplxTrans trans (p->inst_ptr.cell_inst ().complex_trans (*(p->array_inst)));
    abs_trans = abs_trans * trans;

    if (abs_coord) {
      trans = abs_trans;
    }

    std::string line;
    line += cv->layout ().cell_name (p->inst_ptr.cell_index ());
    line += "\tat ";
    line += trans.to_string (true /*lazy*/, dbu_coord ? 0.0 : dbu);
    
    list->addItem (tl::to_qstring (line));

  }
}

// ----------------------------------------------------------------------
//  edt::CopyModeDialogForm implementation

CopyModeDialog::CopyModeDialog (QWidget *parent)
  : QDialog (parent)
{
  setObjectName (QString::fromUtf8 ("copy_mode_dialog"));

  Ui::CopyModeDialog::setupUi (this);
}

CopyModeDialog::~CopyModeDialog ()
{
  //  .. nothing yet ..
}

bool 
CopyModeDialog::exec_dialog (unsigned int &mode)
{
  if (mode == 0) {
    shallow_rb->setChecked (true);
  }
  if (QDialog::exec ()) {
    if (shallow_rb->isChecked ()) {
      mode = 0;
    } else {
      mode = 1;
    }
    return true;
  } else {
    return false;
  }
}

// --------------------------------------------------------------------------------
//  ChangeLayerOptionsDialog implementation

ChangeLayerOptionsDialog::ChangeLayerOptionsDialog (QWidget *parent)
  : QDialog (parent)
{
  setObjectName (QString::fromUtf8 ("change_layer_options_dialog"));

  Ui::ChangeLayerOptionsDialog::setupUi (this);
}

ChangeLayerOptionsDialog::~ChangeLayerOptionsDialog ()
{
  //  .. nothing yet ..
}

bool 
ChangeLayerOptionsDialog::exec_dialog (lay::LayoutView *view, int cv_index, unsigned int &new_layer)
{
  std::vector <std::pair <db::LayerProperties, unsigned int> > ll;

  const db::Layout &layout = view->cellview (cv_index)->layout ();
  for (unsigned int i = 0; i < layout.layers (); ++i) {
    if (layout.is_valid_layer (i)) {
      ll.push_back (std::make_pair (layout.get_properties (i), i));
    }
  }

  std::sort (ll.begin (), ll.end ());

  target_cbx->clear ();
  int initial_sel = -1;
  int i = 0;
  for (std::vector <std::pair <db::LayerProperties, unsigned int> >::const_iterator lp = ll.begin (); lp != ll.end (); ++lp, ++i) {
    if (lp->second == new_layer) {
      initial_sel = i;
    }
    target_cbx->addItem (tl::to_qstring (lay::ParsedLayerSource (lp->first, cv_index).to_string ()));
  }
  target_cbx->setCurrentIndex (initial_sel);

  if (QDialog::exec () && target_cbx->currentIndex () >= 0 && target_cbx->currentIndex () < int (ll.size ())) {
    new_layer = ll [target_cbx->currentIndex ()].second;
    return true;
  } else {
    return false;
  }
}

// --------------------------------------------------------------------------------
//  AlignOptionsDialog implementation

AlignOptionsDialog::AlignOptionsDialog (QWidget *parent)
  : QDialog (parent)
{
  setObjectName (QString::fromUtf8 ("change_layer_options_dialog"));

  Ui::AlignOptionsDialog::setupUi (this);
}

AlignOptionsDialog::~AlignOptionsDialog ()
{
  //  .. nothing yet ..
}

bool 
AlignOptionsDialog::exec_dialog (lay::LayoutView * /*view*/, int &hmode, int &vmode, bool &visible_layers)
{
  QRadioButton *hmode_buttons [] = { this->h_none_rb, this->h_left_rb, this->h_center_rb, this->h_right_rb };
  QRadioButton *vmode_buttons [] = { this->v_none_rb, this->v_top_rb, this->v_center_rb, this->v_bottom_rb };
  QRadioButton *layers_buttons [] = { this->all_layers_rb, this->visible_layers_rb };
  
  for (int i = 0; i < 4; ++i) {
    hmode_buttons [i]->setChecked (hmode == i); 
  }
  for (int i = 0; i < 4; ++i) {
    vmode_buttons [i]->setChecked (vmode == i); 
  }
  for (int i = 0; i < 2; ++i) {
    layers_buttons [i]->setChecked (int (visible_layers) == i); 
  }

  if (QDialog::exec ()) {

    hmode = -1;
    for (int i = 0; i < 4; ++i) {
      if (hmode_buttons [i]->isChecked ()) {
        hmode = i;
      }
    }

    vmode = -1;
    for (int i = 0; i < 4; ++i) {
      if (vmode_buttons [i]->isChecked ()) {
        vmode = i;
      }
    }

    visible_layers = false;
    for (int i = 0; i < 2; ++i) {
      if (layers_buttons [i]->isChecked ()) {
        visible_layers = (i != 0);
      }
    }

    return true;

  } else {
    return false;
  }
}

// --------------------------------------------------------------------------------
//  MakeCellOptionsDialog implementation

MakeCellOptionsDialog::MakeCellOptionsDialog (QWidget *parent)
  : QDialog (parent)
{ 
  setupUi (this);
}

bool 
MakeCellOptionsDialog::exec_dialog (const db::Layout &layout, std::string &name) 
{
  do {
BEGIN_PROTECTED
    if (QDialog::exec ()) {
      name = tl::to_string (cell_name_le->text ());
      if (name.empty ()) {
        throw tl::Exception (tl::to_string (QObject::tr ("Cell name must not be empty")));
      } else if (layout.cell_by_name (name.c_str ()).first) {
        throw tl::Exception (tl::to_string (QObject::tr ("A cell with that name already exists: ")) + name);
      }
      return true;
    } else {
      return false;
    }
END_PROTECTED
  } while (true);
}

// --------------------------------------------------------------------------------
//  RoundCornerOptionsDialog implementation

RoundCornerOptionsDialog::RoundCornerOptionsDialog (QWidget *parent)
  : QDialog (parent), mp_layout (0)
{
  setObjectName (QString::fromUtf8 ("round_corners_options_dialog"));

  Ui::RoundCornerOptionsDialog::setupUi (this);
}

RoundCornerOptionsDialog::~RoundCornerOptionsDialog ()
{
  //  .. nothing yet ..
}

bool 
RoundCornerOptionsDialog::exec_dialog (const db::Layout &layout, double &router, double &rinner, unsigned int &npoints)
{
  mp_layout = &layout;

  router_le->setText (tl::to_qstring (tl::to_string (router)));
  if (fabs (router - rinner) < 1e-6) {
    rinner_le->setText (QString ());
  } else {
    rinner_le->setText (tl::to_qstring (tl::to_string (rinner)));
  }
  points_le->setText (tl::to_qstring (tl::to_string (npoints)));

  if (QDialog::exec ()) {

    tl::from_string (tl::to_string (router_le->text ()), router);
    if (rinner_le->text ().isEmpty ()) {
      rinner = router;
    } else {
      tl::from_string (tl::to_string (rinner_le->text ()), rinner);
    }
    tl::from_string (tl::to_string (points_le->text ()), npoints);

    mp_layout = 0;
    return true;

  } else {
    mp_layout = 0;
    return false;
  }
}

void 
RoundCornerOptionsDialog::accept ()
{
BEGIN_PROTECTED;

  double rhull = 0.0, rholes = 0.0;
  unsigned int npoints = 0;
  
  tl::from_string (tl::to_string (router_le->text ()), rhull);
  if (rinner_le->text ().isEmpty ()) {
    rholes = rhull;
  } else {
    tl::from_string (tl::to_string (rinner_le->text ()), rholes);
  }
  tl::from_string (tl::to_string (points_le->text ()), npoints);

  const unsigned int min_points = 16;
  const double seg_thr = 10.0; // in DBU

  if (npoints < min_points) {
    throw tl::Exception (tl::to_string (QObject::tr ("Number of points is too small (must be %d at least)")), min_points);
  }

  double dbu = mp_layout->dbu ();

  if ((rholes > 0.0 && dbu * seg_thr > (rholes * M_PI * 2.0) / double (npoints)) ||
      (rhull > 0.0 && dbu * seg_thr > (rhull * M_PI * 2.0) / double (npoints))) {
    throw tl::Exception (tl::to_string (QObject::tr ("Number of points is too large (one segment must be larger than %g database units)")), seg_thr);
  }

  if (fabs (rholes - 2.0 * dbu * floor (rholes * 0.5 / dbu + 0.5)) > 1e-6 ||
      fabs (rhull - 2.0 * dbu * floor (rhull * 0.5 / dbu + 0.5)) > 1e-6) {
    throw tl::Exception (tl::to_string (QObject::tr ("Radius must be a even multiple of the database unit")));
  }

  QDialog::accept ();

END_PROTECTED;
}

// --------------------------------------------------------------------------------
//  MakeArrayOptionsDialog implementation

MakeArrayOptionsDialog::MakeArrayOptionsDialog (QWidget *parent)
  : QDialog (parent)
{ 
  setupUi (this);
}

bool 
MakeArrayOptionsDialog::exec_dialog (db::DVector &a, unsigned int &na, db::DVector &b, unsigned int &nb)
{
  rows_le->setText (tl::to_qstring (tl::to_string (na)));
  columns_le->setText (tl::to_qstring (tl::to_string (nb)));
  row_x_le->setText (tl::to_qstring (tl::micron_to_string (a.x ())));
  row_y_le->setText (tl::to_qstring (tl::micron_to_string (a.y ())));
  column_x_le->setText (tl::to_qstring (tl::micron_to_string (b.x ())));
  column_y_le->setText (tl::to_qstring (tl::micron_to_string (b.y ())));

  if (QDialog::exec ()) {

    double bx = 0.0, by = 0.0;
    double ax = 0.0, ay = 0.0;

    tl::from_string (tl::to_string (column_x_le->text ()), bx);
    tl::from_string (tl::to_string (column_y_le->text ()), by);
    tl::from_string (tl::to_string (columns_le->text ()), nb);
    tl::from_string (tl::to_string (row_x_le->text ()), ax);
    tl::from_string (tl::to_string (row_y_le->text ()), ay);
    tl::from_string (tl::to_string (rows_le->text ()), na);

    a = db::DVector (ax, ay);
    b = db::DVector (bx, by);

    return true;

  } else {
    return false;
  }

}

void 
MakeArrayOptionsDialog::accept ()
{
BEGIN_PROTECTED;

  double bx = 0.0, by = 0.0;
  double ax = 0.0, ay = 0.0;
  int na, nb;

  tl::from_string (tl::to_string (column_x_le->text ()), bx);
  tl::from_string (tl::to_string (column_y_le->text ()), by);
  tl::from_string (tl::to_string (columns_le->text ()), nb);
  tl::from_string (tl::to_string (row_x_le->text ()), ax);
  tl::from_string (tl::to_string (row_y_le->text ()), ay);
  tl::from_string (tl::to_string (rows_le->text ()), na);

  if (na < 1 || nb < 1) {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid row or column count (must be larger or equal one)")));
  }

  QDialog::accept ();

END_PROTECTED;
}

}

