
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2026 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 "layXORToolDialog.h"
#include "layXORProgress.h"
#include "antService.h"
#include "rdb.h"
#include "rdbUtils.h"
#include "dbShapeProcessor.h"
#include "dbRecursiveShapeIterator.h"
#include "dbClip.h"
#include "dbLayoutUtils.h"
#include "dbRegion.h"
#include "dbDeepShapeStore.h"
#include "tlTimer.h"
#include "tlProgress.h"
#include "tlThreadedWorkers.h"
#include "tlEnv.h"
#include "tlExceptions.h"
#include "tlMath.h"
#include "layCellView.h"
#include "layLayoutView.h"
#include "layApplication.h"
#include "layMainWindow.h"

#include "ui_XORToolDialog.h"

#include <stdio.h>

#include <QMessageBox>

namespace lay
{

bool merge_before_bool ()
{
  //  $KLAYOUT_XOR_MERGE_BEFORE_BOOLEAN
  return tl::app_flag ("xor-merge-before-boolean");
}

std::string cfg_xor_input_mode ("xor-input-mode");
std::string cfg_xor_output_mode ("xor-output-mode");
std::string cfg_xor_nworkers ("xor-num-workers");
std::string cfg_xor_layer_offset ("xor-layer-offset");
std::string cfg_xor_axorb ("xor-axorb");
std::string cfg_xor_anotb ("xor-anotb");
std::string cfg_xor_bnota ("xor-bnota");
std::string cfg_xor_summarize ("xor-summarize");
std::string cfg_xor_tolerances ("xor-tolerances");
std::string cfg_xor_deep ("xor-deep");
std::string cfg_xor_tiling ("xor-tiling");
std::string cfg_xor_tiling_heal ("xor-tiling-heal");
std::string cfg_xor_region_mode ("xor-region-mode");

//  Note: this enum must match with the order of the combo box entries in the 
//  dialog implementation
enum input_mode_t { IMAll = 0, IMVisible, IMSpecific };

//  Note: this enum must match with the order of the combo box entries in the 
//  dialog implementation
enum output_mode_t { OMMarkerDatabase = 0, OMNewLayout, OMNewLayersA, OMNewLayersB, OMNone };

//  Note: this enum must match with the order of the combo box entries in the 
//  dialog implementation
enum region_mode_t { RMAll = 0, RMVisible, RMRulers };

struct InputModeConverter
{
  std::string to_string (input_mode_t t)
  {
    switch (t) {
    case IMAll:
      return "all";
    case IMVisible:
      return "visible";
    case IMSpecific:
      return "specific";
    default:
      return "";
    }
  }

  void from_string (const std::string &s, input_mode_t &t) 
  {
    t = IMAll;
    if (s == "visible") {
      t = IMVisible;
    } else if (s == "specific") {
      t = IMSpecific;
    }
  }
};

struct OutputModeConverter
{
  std::string to_string (output_mode_t t)
  {
    switch (t) {
    case OMMarkerDatabase:
      return "rdb";
    case OMNewLayout:
      return "layout";
    case OMNewLayersA:
      return "layers-a";
    case OMNewLayersB:
      return "layers-b";
    default:
      return "";
    }
  }

  void from_string (const std::string &s, output_mode_t &t) 
  {
    t = OMMarkerDatabase;
    if (s == "layout") {
      t = OMNewLayout;
    } else if (s == "layers-a") {
      t = OMNewLayersA;
    } else if (s == "layers-b") {
      t = OMNewLayersB;
    }
  }
};

struct RegionModeConverter
{
  std::string to_string (region_mode_t t)
  {
    switch (t) {
    case RMAll:
      return "all";
    case RMVisible:
      return "visible";
    case RMRulers:
      return "rulers";
    default:
      return "";
    }
  }

  void from_string (const std::string &s, region_mode_t &t) 
  {
    t = RMAll;
    if (s == "all") {
      t = RMAll;
    } else if (s == "visible") {
      t = RMVisible;
    } else if (s == "rulers") {
      t = RMRulers;
    }
  }
};

XORToolDialog::XORToolDialog (QWidget *parent)
  : QDialog (parent), mp_view (0)
{
  mp_ui = new Ui::XORToolDialog ();
  mp_ui->setupUi (this);

  connect (mp_ui->input_layers_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (input_changed (int)));
  connect (mp_ui->output_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (output_changed (int)));
  connect (mp_ui->deep, SIGNAL (clicked ()), this, SLOT (deep_changed ()));

  input_changed (0);
  output_changed (0);
}

XORToolDialog::~XORToolDialog ()
{
  delete mp_ui;
  mp_ui = 0;
}

int 
XORToolDialog::exec_dialog (lay::LayoutViewBase *view)
{
  mp_view = view;

  if (view != mp_ui->layouta->layout_view () || view != mp_ui->layoutb->layout_view ()) {

    mp_ui->layouta->set_layout_view (view);
    mp_ui->layoutb->set_layout_view (view);

    if (view->cellviews () >= 2) {
      mp_ui->layouta->set_current_cv_index (0);
      mp_ui->layoutb->set_current_cv_index (1);
    }

  } else {
    //  force update of the layer list
    //  TODO: the controls should register a listener for the view so this activity is not necessary:
    mp_ui->layouta->set_layout_view (view);
    mp_ui->layoutb->set_layout_view (view);
  }

  //  take current settings from the configurations
  lay::Dispatcher *config_root = lay::Dispatcher::instance ();

  input_mode_t im = IMAll;
  if (config_root->config_get (cfg_xor_input_mode, im, InputModeConverter ())) {
    mp_ui->input_layers_cbx->setCurrentIndex ((int) im);
  }

  output_mode_t om = OMMarkerDatabase;
  if (config_root->config_get (cfg_xor_output_mode, om, OutputModeConverter ())) {
    mp_ui->output_cbx->setCurrentIndex ((int) om);
  }

  region_mode_t rm = RMAll;
  if (config_root->config_get (cfg_xor_region_mode, rm, RegionModeConverter ())) {
    mp_ui->region_cbx->setCurrentIndex ((int) rm);
  }

  int nw = 1;
  if (config_root->config_get (cfg_xor_nworkers, nw)) {
    mp_ui->threads->setValue (nw);
  }

  std::string lo;
  if (config_root->config_get (cfg_xor_layer_offset, lo)) {
    mp_ui->layer_offset_le->setText (tl::to_qstring (lo));
  }

  bool f = false;
  if (config_root->config_get (cfg_xor_axorb, f)) {
    mp_ui->axorb_cb->setChecked (f);
  }
  if (config_root->config_get (cfg_xor_anotb, f)) {
    mp_ui->anotb_cb->setChecked (f);
  }
  if (config_root->config_get (cfg_xor_bnota, f)) {
    mp_ui->bnota_cb->setChecked (f);
  }

  bool summarize = false;
  if (config_root->config_get (cfg_xor_summarize, summarize)) {
    mp_ui->summarize_cb->setChecked (summarize);
  }

  std::string tol;
  if (config_root->config_get (cfg_xor_tolerances, tol)) {
    mp_ui->tolerances->setText (tl::to_qstring (tol));
  }

  bool deep = false;
  if (config_root->config_get (cfg_xor_deep, deep)) {
    mp_ui->deep->setChecked (deep);
  }
  deep_changed ();

  std::string tiling;
  if (config_root->config_get (cfg_xor_tiling, tiling)) {
    mp_ui->tiling->setText (tl::to_qstring (tiling));
  }

  bool heal = false;
  if (config_root->config_get (cfg_xor_tiling_heal, heal)) {
    mp_ui->heal_cb->setChecked (heal);
  }

  int ret = QDialog::exec ();

  if (ret) {
    run_xor ();
  }

  mp_view = 0;
  return ret;
}

void 
XORToolDialog::accept ()
{
BEGIN_PROTECTED

  bool axorb = mp_ui->axorb_cb->isChecked ();
  bool anotb = mp_ui->anotb_cb->isChecked ();
  bool bnota = mp_ui->bnota_cb->isChecked ();
  if (axorb + anotb + bnota == 0) {
    throw tl::Exception (tl::to_string (QObject::tr ("No mode selected")));  
  }

  int cv_index_a = mp_ui->layouta->current_cv_index ();
  int cv_index_b = mp_ui->layoutb->current_cv_index ();

  const lay::CellView &cva = mp_view->cellview (cv_index_a);
  const lay::CellView &cvb = mp_view->cellview (cv_index_b);

  if (&cva->layout () == &cvb->layout () && cva.cell_index () == cvb.cell_index ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("Trying to perform an XOR between identical layouts")));  
  }

  if (!cva.is_valid ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("First layout is not a valid input")));  
  }
  if (!cvb.is_valid ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("Second layout is not a valid input")));  
  }

  {
    std::string text (tl::to_string (mp_ui->tolerances->text ()));
    tl::Extractor ex (text.c_str ());
    while (! ex.at_end ()) {
      double t;
      if (! ex.try_read (t) || t < -1e-6) {
        break;
      }
      ex.test (",");
    }
  }

  {
    std::string text (tl::to_string (mp_ui->tiling->text ()));
    tl::Extractor ex (text.c_str ());
    double t = 0.0;
    if (ex.try_read (t)) {
      if (t < 0.001) {
        throw tl::Exception (tl::to_string (QObject::tr ("Invalid tile size (invalid text or negative)")));  
      }
    }
  }
  
  lay::Dispatcher *config_root = lay::Dispatcher::instance ();

  config_root->config_set (cfg_xor_input_mode, InputModeConverter ().to_string ((input_mode_t) mp_ui->input_layers_cbx->currentIndex ()));
  config_root->config_set (cfg_xor_output_mode, OutputModeConverter ().to_string ((output_mode_t) mp_ui->output_cbx->currentIndex ()));
  config_root->config_set (cfg_xor_region_mode, RegionModeConverter ().to_string ((region_mode_t) mp_ui->region_cbx->currentIndex ()));
  config_root->config_set (cfg_xor_axorb, mp_ui->axorb_cb->isChecked ());
  config_root->config_set (cfg_xor_anotb, mp_ui->anotb_cb->isChecked ());
  config_root->config_set (cfg_xor_bnota, mp_ui->bnota_cb->isChecked ());
  config_root->config_set (cfg_xor_nworkers, mp_ui->threads->value ());
  config_root->config_set (cfg_xor_layer_offset, tl::to_string (mp_ui->layer_offset_le->text ()));
  config_root->config_set (cfg_xor_summarize, mp_ui->summarize_cb->isChecked ());
  config_root->config_set (cfg_xor_tolerances, tl::to_string (mp_ui->tolerances->text ()));
  config_root->config_set (cfg_xor_deep, mp_ui->deep->isChecked ());
  config_root->config_set (cfg_xor_tiling, tl::to_string (mp_ui->tiling->text ()));
  config_root->config_set (cfg_xor_tiling_heal, mp_ui->heal_cb->isChecked ());
  config_root->config_end ();

  QDialog::accept ();

END_PROTECTED
}

void
XORToolDialog::deep_changed ()
{
  bool deep = mp_ui->deep->isChecked ();
  mp_ui->tiling->setEnabled (!deep);
  mp_ui->heal_cb->setEnabled (!deep);
}

void
XORToolDialog::input_changed (int /*index*/)
{
  // .. nothing yet ..
}

void
XORToolDialog::output_changed (int index)
{
  bool enabled = (index == 2 || index == 3);
  mp_ui->layer_offset_lbl->setEnabled (enabled);
  mp_ui->layer_offset_le->setEnabled (enabled);
}


class XORJob
  : public tl::JobBase
{
public:
  enum EmptyLayerHandling
  {
    EL_optimize,        // copy the non-empty contributions of a or b
    EL_summarize,       // print a message about leaving away some operations and don't do anything
    EL_process          // include in processing - the non-empty layer will be merged
  };

  XORJob (int nworkers, 
          output_mode_t output_mode, 
          db::BooleanOp::BoolOp op,
          EmptyLayerHandling el_handling,
          double dbu,
          const lay::CellView &cva, const lay::CellView &cvb,
          const std::vector <db::Coord> &tolerances,
          const std::vector <rdb::Category *> &sub_categories,
          const std::vector <std::vector <rdb::Category *> > &layer_categories,
          const std::vector <db::Cell *> &sub_cells,
          const std::vector <std::vector <unsigned int> > &sub_output_layers,
          rdb::Database *rdb,
          rdb::Cell *rdb_cell)
    : tl::JobBase (nworkers),
      m_output_mode (output_mode),
      m_op (op),
      m_el_handling (el_handling),
      m_has_tiles (false),
      m_tile_heal (false),
      m_dbu (dbu),
      m_cva (cva),
      m_cvb (cvb),
      m_tolerances (tolerances),
      m_sub_categories (sub_categories),
      m_layer_categories (layer_categories),
      m_sub_cells (sub_cells),
      m_sub_output_layers (sub_output_layers),
      m_rdb (rdb),
      m_rdb_cell (rdb_cell),
      m_progress (0),
      m_nx (0), m_ny (0)
  {
  }

  output_mode_t output_mode () const
  {
    return m_output_mode;
  }

  db::BooleanOp::BoolOp op () const
  {
    return m_op;
  }

  EmptyLayerHandling el_handling () const
  {
    return m_el_handling;
  }

  bool has_tiles () const
  {
    return m_has_tiles;
  }

  void set_tiles (bool ht, int nx, int ny, bool heal)
  {
    m_has_tiles = ht;
    m_nx = ht ? nx : 0;
    m_ny = ht ? ny : 0;
    m_tile_heal = heal;
  }

  double dbu () const
  {
    return m_dbu;
  }

  const lay::CellView &cva () const
  {
    return m_cva;
  }

  const lay::CellView &cvb () const
  {
    return m_cvb;
  }

  const std::vector <db::Coord> &tolerances () const
  {
    return m_tolerances;
  }

  const std::vector <std::vector <unsigned int> > &sub_output_layers () const
  {
    return m_sub_output_layers;
  }

  void next_progress () 
  {
    QMutexLocker locker (&m_mutex);
    ++m_progress;
  }

  void add_results (const db::LayerProperties &lp, db::Coord tol, size_t n, size_t ix, size_t iy)
  {
    QMutexLocker locker (&m_mutex);

    std::vector<std::vector<size_t> > &cc = m_results [std::make_pair (lp, tol)];
    if (cc.size () <= ix) {
      cc.resize (ix + 1, std::vector<size_t> ());
    }
    if (cc [ix].size () <= iy) {
      cc [ix].resize (iy + 1, 0);
    }

    if (n == missing_in_a || n == missing_in_b) {
      cc[ix][iy] = n;
    } else {
      //  NOTE: we will not get a "normal" n after missing_in_a or missing_in_b
      cc[ix][iy] += n;
    }
  }

  void update_progress (XORProgress &progress)
  {
    unsigned int p;
    {
      QMutexLocker locker (&m_mutex);
      p = m_progress;
      progress.configure (m_dbu, int (m_nx), int (m_ny), m_tolerances);
      progress.merge_results (m_results);
    }    

    progress.set (p, true /*force yield*/);
  }

  void issue_string (unsigned int tol_index, unsigned int layer_index, const std::string &s)
  {
    QMutexLocker locker (&m_mutex);

    if (m_output_mode == OMMarkerDatabase) {

      rdb::Category *layercat = m_layer_categories[tol_index][layer_index];

      rdb::Item *item = m_rdb->create_item (m_rdb_cell->id (), layercat->id ());
      item->values ().add (new rdb::Value <std::string> (s));

    }
  }

  void issue_region (unsigned int tol_index, unsigned int layer_index, const db::Region &region)
  {
    QMutexLocker locker (&m_mutex);
    db::CplxTrans trans (dbu ());

    if (m_output_mode == OMMarkerDatabase) {

      rdb::Category *layercat = m_layer_categories[tol_index][layer_index];

      std::pair<db::RecursiveShapeIterator, db::ICplxTrans> it = region.begin_iter ();
      rdb::scan_layer (layercat, m_rdb_cell, trans * it.second, it.first, false);

    } else {

      db::Cell *output_cell = m_sub_cells[tol_index];
      unsigned int output_layer = m_sub_output_layers[tol_index][layer_index];

      region.insert_into (output_cell->layout (), output_cell->cell_index (), output_layer);

    }
  }

  void issue_polygon (unsigned int tol_index, unsigned int layer_index, const db::Polygon &polygon, bool touches_border = false)
  {
    QMutexLocker locker (&m_mutex);
    db::CplxTrans trans (dbu ());

    if (m_tile_heal && touches_border) {

      //  save for merging later
      m_polygons_to_heal [std::make_pair (tol_index, layer_index)].insert (polygon);

    } else if (m_output_mode == OMMarkerDatabase) {

      rdb::Category *layercat = m_layer_categories[tol_index][layer_index];

      rdb::Item *item = m_rdb->create_item (m_rdb_cell->id (), layercat->id ());
      item->values ().add (new rdb::Value <db::DPolygon> (polygon.transformed (trans)));

    } else {

      db::Cell *subcell = 0;
      unsigned int layout_layer = 0;

      subcell = m_sub_cells[tol_index];
      layout_layer = m_sub_output_layers[tol_index][layer_index];

      double factor = 1.0;
      if (subcell->layout ()) {
        factor = dbu () / subcell->layout ()->dbu ();
      }
      if (tl::equal (factor, 1.0)) {
        subcell->shapes (layout_layer).insert (polygon);
      } else {
        subcell->shapes (layout_layer).insert (db::Polygon (polygon * factor));
      }

    }
  }

  void finish ()
  {
    //  merge the polygons to heal and re-issue (this time without healing)
    for (std::map<std::pair<size_t, size_t>, db::Region>::iterator p = m_polygons_to_heal.begin (); p != m_polygons_to_heal.end (); ++p) {
      for (db::Region::const_iterator mp = p->second.begin_merged (); !mp.at_end (); ++mp) {
        issue_polygon ((unsigned int) p->first.first, (unsigned int) p->first.second, *mp, false);
      }
    }
  }

  virtual tl::Worker *create_worker ();

private:
  output_mode_t m_output_mode;
  db::BooleanOp::BoolOp m_op;
  EmptyLayerHandling m_el_handling;
  bool m_has_tiles;
  bool m_tile_heal;
  double m_dbu;
  lay::CellView m_cva;
  lay::CellView m_cvb;
  std::vector <db::Coord> m_tolerances;
  std::vector <rdb::Category *> m_sub_categories;
  std::vector <std::vector <rdb::Category *> > m_layer_categories;
  std::vector <db::Cell *> m_sub_cells;
  std::vector <std::vector <unsigned int> > m_sub_output_layers;
  rdb::Database *m_rdb;
  rdb::Cell *m_rdb_cell;
  unsigned int m_progress;
  QMutex m_mutex;
  std::string m_result_string;
  size_t m_nx, m_ny;
  std::map<std::pair<db::LayerProperties, db::Coord>, std::vector<std::vector<size_t> > > m_results;
  std::map<std::pair<size_t, size_t>, db::Region> m_polygons_to_heal;
};

class XORTask
  : public tl::Task
{
public:
  XORTask (bool deep, const std::string &tile_desc, const db::Box &clip_box, const db::Box &region_a, const db::Box &region_b, unsigned int layer_index, const db::LayerProperties &lp, const std::vector<unsigned int> &la, const std::vector<unsigned int> &lb, int ix, int iy)
    : m_deep (deep), m_tile_desc (tile_desc), m_clip_box (clip_box), m_region_a (region_a), m_region_b (region_b), m_layer_index (layer_index), m_lp (lp), m_la (la), m_lb (lb), m_ix (ix), m_iy (iy)
  {
    //  .. nothing yet ..
  }

  bool deep () const
  {
    return m_deep;
  }

  const std::string &tile_desc () const
  {
    return m_tile_desc;
  }
  
  const db::Box &clip_box () const
  {
    return m_clip_box;
  }
  
  const db::Box &region_a () const
  {
    return m_region_a;
  }
  
  const db::Box &region_b () const
  {
    return m_region_b;
  }

  const std::vector<unsigned int> &la () const 
  {
    return m_la;
  }

  const std::vector<unsigned int> &lb () const 
  {
    return m_lb;
  }

  unsigned int layer_index () const
  {
    return m_layer_index;
  }

  const db::LayerProperties &lp () const
  {
    return m_lp;
  }

  int ix () const
  {
    return m_ix;
  }

  int iy () const
  {
    return m_iy;
  }

private:
  bool m_deep;
  std::string m_tile_desc;
  db::Box m_clip_box, m_region_a, m_region_b;
  unsigned int m_layer_index;
  db::LayerProperties m_lp;
  std::vector<unsigned int> m_la, m_lb;
  int m_ix, m_iy;
};

class XORWorker
  : public tl::Worker
{
public:
  XORWorker (XORJob *job)
    : tl::Worker (), mp_job (job)
  {
    //  .. nothing yet ..
  }

  void perform_task (tl::Task *task) 
  {
    XORTask *xor_task = dynamic_cast <XORTask *> (task);
    if (xor_task) {
      do_perform (xor_task);
    }
  }

private:
  XORJob *mp_job;

  void do_perform (const XORTask *task);
  void do_perform_tiled (const XORTask *task);
  void do_perform_deep (const XORTask *task);
};

void
XORWorker::do_perform (const XORTask *xor_task)
{
  if (xor_task->deep ()) {
    do_perform_deep (xor_task);
  } else {
    do_perform_tiled (xor_task);
  }
}

void
XORWorker::do_perform_deep (const XORTask *xor_task)
{
  db::DeepShapeStore dss;
  db::Region rr;

  unsigned int tol_index = 0;
  for (std::vector <db::Coord>::const_iterator t = mp_job->tolerances ().begin (); t != mp_job->tolerances ().end (); ++t, ++tol_index) {

    const std::vector<unsigned int> &la = xor_task->la ();
    const std::vector<unsigned int> &lb = xor_task->lb ();

    if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () != XORJob::EL_summarize) {

      if (tl::verbosity () >= 10) {
        tl::info << "XOR tool (hierarchical): layer " << xor_task->lp ().to_string () << ", tolerance " << *t * mp_job->dbu ();
      }

      tl::SelfTimer timer (tl::verbosity () >= 11, "Elapsed time");

      if (tol_index == 0) {

        if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () == XORJob::EL_process) {

          tl::SelfTimer timer (tl::verbosity () >= 21, "Boolean part");

          db::RecursiveShapeIterator s_a (mp_job->cva ()->layout (), mp_job->cva ()->layout ().cell (mp_job->cva ().cell_index ()), la, xor_task->region_a ());
          db::RecursiveShapeIterator s_b (mp_job->cvb ()->layout (), mp_job->cvb ()->layout ().cell (mp_job->cvb ().cell_index ()), lb, xor_task->region_b ());

          s_a.set_for_merged_input (true);
          s_b.set_for_merged_input (true);

          db::Region ra (s_a, dss, db::ICplxTrans (mp_job->cva ()->layout ().dbu () / mp_job->dbu ()));
          db::Region rb (s_b, dss, db::ICplxTrans (mp_job->cvb ()->layout ().dbu () / mp_job->dbu ()));

          if (mp_job->op () == db::BooleanOp::Xor) {
            rr = ra ^ rb;
          } else if (mp_job->op () == db::BooleanOp::ANotB) {
            rr = ra - rb;
          } else if (mp_job->op () == db::BooleanOp::BNotA) {
            rr = rb - ra;
          }

        } else if (mp_job->op () == db::BooleanOp::Xor ||
                   (mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
                   (mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {

          tl::SelfTimer timer (tl::verbosity () >= 21, "Boolean part (shortcut)");

          db::RecursiveShapeIterator s;
          db::ICplxTrans dbu_scale;

          if (!la.empty ()) {
            s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
            dbu_scale = db::ICplxTrans (mp_job->cva ()->layout ().dbu () / mp_job->dbu ());
          } else if (!lb.empty ()) {
            s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
            dbu_scale = db::ICplxTrans (mp_job->cvb ()->layout ().dbu () / mp_job->dbu ());
          }

          s.set_for_merged_input (true);

          rr = db::Region (s, dss, dbu_scale);

        }

      }

      if (*t > 0) {
        tl::SelfTimer timer (tl::verbosity () >= 21, "Sizing part");
        rr.size (-((*t + 1) / 2), (unsigned int)2);
        rr.size (((*t + 1) / 2), (unsigned int)2);
      }

      //  TODO: no clipping for hierarchical mode yet
      mp_job->issue_region (tol_index, xor_task->layer_index (), rr);

      mp_job->add_results (xor_task->lp (), *t, rr.count (), xor_task->ix (), xor_task->iy ());

    } else if (mp_job->op () == db::BooleanOp::Xor ||
               (mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
               (mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {

      if (!la.empty ()) {
        mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout B")));
        mp_job->add_results (xor_task->lp (), *t, missing_in_b, 0, 0);
      }

      if (!lb.empty ()) {
        mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout A")));
        mp_job->add_results (xor_task->lp (), *t, missing_in_a, 0, 0);
      }

    }

    mp_job->next_progress ();

  }
}

void
XORWorker::do_perform_tiled (const XORTask *xor_task)
{
  db::ShapeProcessor sp (true);

  //  prepare a layout for the results
  db::Layout xor_results;
  xor_results.dbu (mp_job->dbu ());
  db::Cell &xor_results_cell = xor_results.cell (xor_results.add_cell ());
  xor_results.insert_layer (0);

  unsigned int tol_index = 0;
  for (std::vector <db::Coord>::const_iterator t = mp_job->tolerances ().begin (); t != mp_job->tolerances ().end (); ++t, ++tol_index) {

    const std::vector<unsigned int> &la = xor_task->la ();
    const std::vector<unsigned int> &lb = xor_task->lb ();

    if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () != XORJob::EL_summarize) {

      if (tl::verbosity () >= (mp_job->has_tiles () ? 20 : 10)) {
        tl::info << "XOR tool: layer " << xor_task->lp ().to_string () << ", tolerance " << *t * mp_job->dbu () << ", tile " << xor_task->tile_desc ();
      }

      tl::SelfTimer timer (tl::verbosity () >= (mp_job->has_tiles () ? 21 : 11), "Elapsed time");

      if (tol_index == 0) {

        if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () == XORJob::EL_process) {

          tl::SelfTimer timer (tl::verbosity () >= 31, "Boolean part");
          size_t n;

          if (! merge_before_bool ()) {

            //  Straightforward implementation
            sp.clear ();

            db::CplxTrans dbu_scale_a (mp_job->cva ()->layout ().dbu () / xor_results.dbu ());
            db::CplxTrans dbu_scale_b (mp_job->cvb ()->layout ().dbu () / xor_results.dbu ());

            n = 0;
            db::RecursiveShapeIterator s_a;
            if (mp_job->has_tiles ()) {
              s_a = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
            } else {
              s_a = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la);
            }
            s_a.set_for_merged_input (true);
            for ( ; ! s_a.at_end (); ++s_a, ++n) {
              sp.insert (s_a.shape (), dbu_scale_a * s_a.trans (), n * 2);
            }

            n = 0;
            db::RecursiveShapeIterator s_b;
            if (mp_job->has_tiles ()) {
              s_b = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
            } else {
              s_b = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb);
            }
            s_b.set_for_merged_input (true);
            for (; ! s_b.at_end (); ++s_b, ++n) {
              sp.insert (s_b.shape (), dbu_scale_b * s_b.trans (), n * 2 + 1);
            }

            db::BooleanOp bool_op (mp_job->op ());
            db::ShapeGenerator sg (xor_results_cell.shapes (0), true /*clear shapes*/);
            db::PolygonGenerator out (sg, false /*don't resolve holes*/, false /*no min. coherence*/);
            sp.process (out, bool_op);

          } else {

            //  This implementation is faster when a lot of overlapping shapes are involved
            db::Layout merge_helper;
            merge_helper.dbu (mp_job->dbu ());
            db::Cell &merge_helper_cell = merge_helper.cell (merge_helper.add_cell ());
            merge_helper.insert_layer (0);
            merge_helper.insert_layer (1);

            //  This implementation is faster when a lot of overlapping shapes are involved
            if (!la.empty ()) {

              sp.clear ();

              db::CplxTrans dbu_scale (mp_job->cva ()->layout ().dbu () / xor_results.dbu ());

              n = 0;
              db::RecursiveShapeIterator s;
              if (mp_job->has_tiles ()) {
                s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
              } else {
                s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la);
              }
              s.set_for_merged_input (true);
              for ( ; ! s.at_end (); ++s, ++n) {
                sp.insert (s.shape (), dbu_scale * s.trans (), n);
              }

              db::MergeOp op (0);
              db::ShapeGenerator sg (merge_helper_cell.shapes (0), true /*clear shapes*/);
              db::PolygonGenerator out (sg, false /*don't resolve holes*/, false /*no min. coherence*/);
              sp.process (out, op);

            }

            if (!lb.empty ()) {

              sp.clear ();

              db::CplxTrans dbu_scale (mp_job->cvb ()->layout ().dbu () / xor_results.dbu ());

              n = 0;
              db::RecursiveShapeIterator s;
              if (mp_job->has_tiles ()) {
                s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
              } else {
                s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb);
              }
              s.set_for_merged_input (true);
              for ( ; ! s.at_end (); ++s, ++n) {
                sp.insert (s.shape (), dbu_scale * s.trans (), n);
              }

              db::MergeOp op (0);
              db::ShapeGenerator sg (merge_helper_cell.shapes (1), true /*clear shapes*/);
              db::PolygonGenerator out (sg, false /*don't resolve holes*/, false /*no min. coherence*/);
              sp.process (out, op);

            }

            sp.boolean (merge_helper, merge_helper_cell, 0,
                        merge_helper, merge_helper_cell, 1,
                        xor_results_cell.shapes (0), mp_job->op (), true, false, true);

          }

        } else if (mp_job->op () == db::BooleanOp::Xor || 
                   (mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
                   (mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {

          db::RecursiveShapeIterator s;
          db::CplxTrans dbu_scale;

          if (!la.empty ()) {
            if (mp_job->has_tiles ()) {
              s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
            } else {
              s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la);
            }
            dbu_scale = db::CplxTrans (mp_job->cva ()->layout ().dbu () / xor_results.dbu ());
          } else if (!lb.empty ()) {
            if (mp_job->has_tiles ()) {
              s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
            } else {
              s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb);
            }
            dbu_scale = db::CplxTrans (mp_job->cvb ()->layout ().dbu () / xor_results.dbu ());
          }

          s.set_for_merged_input (true);

          for (; ! s.at_end (); ++s) {
            if (s->is_polygon () || s->is_box () || s->is_path ()) {
              db::Polygon p;
              s->polygon (p);
              p.transform (dbu_scale * s.trans ());
              xor_results_cell.shapes (0).insert (p);
            }
          }

        }

      }

      if (*t > 0) {
        tl::SelfTimer timer (tl::verbosity () >= (mp_job->has_tiles () ? 31 : 21), "Sizing part");
        sp.size (xor_results, xor_results_cell, 0, xor_results_cell.shapes (0), -((*t + 1) / 2), (unsigned int)2, false);
        sp.size (xor_results, xor_results_cell, 0, xor_results_cell.shapes (0), ((*t + 1) / 2), (unsigned int)2, false);
      }

      size_t n = 0;

      for (db::Shapes::shape_iterator s = xor_results_cell.shapes (0).begin (db::ShapeIterator::All); ! s.at_end (); ++s) {

        if (mp_job->has_tiles ()) {

          std::vector <db::Polygon> clipped_poly;
          clip_poly (s->polygon (), xor_task->clip_box (), clipped_poly, false /*don't resolve holes*/);
          db::Box inner = xor_task->clip_box ().enlarged (db::Vector (-1, -1));

          for (std::vector <db::Polygon>::const_iterator cp = clipped_poly.begin (); cp != clipped_poly.end (); ++cp) {

            mp_job->issue_polygon (tol_index, xor_task->layer_index (), *cp, !cp->box ().inside (inner));
            ++n;
          }
        
        } else {
          mp_job->issue_polygon (tol_index, xor_task->layer_index (), s->polygon ());
          ++n;
        }

      }

      mp_job->add_results (xor_task->lp (), *t, n, xor_task->ix (), xor_task->iy ());

    } else if (mp_job->op () == db::BooleanOp::Xor || 
               (mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
               (mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {

      if (!la.empty ()) {
        mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout B")));
        mp_job->add_results (xor_task->lp (), *t, missing_in_b, 0, 0);
      }

      if (!lb.empty ()) {
        mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout A")));
        mp_job->add_results (xor_task->lp (), *t, missing_in_a, 0, 0);
      }

    }
    
    mp_job->next_progress ();

  }

}

tl::Worker *
XORJob::create_worker ()
{
  return new XORWorker (this);
}

void 
XORToolDialog::run_xor ()
{
  input_mode_t input_mode = (input_mode_t) mp_ui->input_layers_cbx->currentIndex ();
  output_mode_t output_mode = (output_mode_t) mp_ui->output_cbx->currentIndex ();
  region_mode_t region_mode = (region_mode_t) mp_ui->region_cbx->currentIndex ();

  int nworkers = mp_ui->threads->value ();

  db::LayerOffset layer_offset;
  if (output_mode == OMNewLayersA || output_mode == OMNewLayersB) {
    std::string lo = tl::to_string (mp_ui->layer_offset_le->text ());
    tl::Extractor ex (lo.c_str ());
    layer_offset.read (ex);
  }

  bool axorb = mp_ui->axorb_cb->isChecked ();
  bool anotb = mp_ui->anotb_cb->isChecked ();
  bool bnota = mp_ui->bnota_cb->isChecked ();

  bool deep = mp_ui->deep->isChecked ();

  bool summarize = mp_ui->summarize_cb->isChecked ();
  //  TODO: make this a user interface feature later
  bool process_el = tl::app_flag ("always-do-xor");

  int cv_index_a = mp_ui->layouta->current_cv_index ();
  int cv_index_b = mp_ui->layoutb->current_cv_index ();

  lay::CellView cva = mp_view->cellview (cv_index_a);
  lay::CellView cvb = mp_view->cellview (cv_index_b);

  //  NOTE: basically we should take the common denominator rather than the minimum of the layout's DBU's. 
  //  But this could be a very small number resulting in coordinate overflow issues. 
  double dbu = std::min (cva->layout ().dbu (), cvb->layout ().dbu ());

  std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc> layers;

  for (db::Layout::layer_iterator la = cva->layout ().begin_layers (); la != cva->layout ().end_layers (); ++la) {
    layers[*(*la).second].first.push_back ((*la).first);
  }

  for (db::Layout::layer_iterator lb = cvb->layout ().begin_layers (); lb != cvb->layout ().end_layers (); ++lb) {
    layers[*(*lb).second].second.push_back ((*lb).first);
  }

  //  Keep only visible layers if requested. Treat invisible ones as empty.
  if (input_mode == IMVisible) {

    std::set<unsigned int> visible_layers_a;
    std::set<unsigned int> visible_layers_b;

    for (lay::LayerPropertiesConstIterator l = mp_view->begin_layers (); l != mp_view->end_layers (); ++l) {
      if (! l->has_children () && l->layer_index () >= 0 && l->cellview_index () == cv_index_a && l->visible (true)) {
        visible_layers_a.insert (l->layer_index ());
      }
      if (! l->has_children () && l->layer_index () >= 0 && l->cellview_index () == cv_index_b && l->visible (true)) {
        visible_layers_b.insert (l->layer_index ());
      }
    }

    for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::iterator lm = layers.begin (); lm != layers.end (); ) {

      std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::iterator lm_next = lm;
      ++lm_next;

      std::sort (lm->second.first.begin (), lm->second.first.end ());
      std::sort (lm->second.second.begin (), lm->second.second.end ());

      lm->second.first.erase (std::set_intersection (lm->second.first.begin (), lm->second.first.end (), visible_layers_a.begin (), visible_layers_a.end (), lm->second.first.begin ()), lm->second.first.end ());
      lm->second.second.erase (std::set_intersection (lm->second.second.begin (), lm->second.second.end (), visible_layers_b.begin (), visible_layers_b.end (), lm->second.second.begin ()), lm->second.second.end ());

      if (lm->second.first.empty () && lm->second.second.empty ()) {
        layers.erase (lm);
      }

      lm = lm_next;

    }

  }

  std::vector <db::Coord> tolerances;

  {
    std::string text (tl::to_string (mp_ui->tolerances->text ()));
    tl::Extractor ex (text.c_str ());
    while (! ex.at_end ()) {
      double t;
      if (! ex.try_read (t) || t < -1e-6) {
        break;
      }
      ex.test (",");
      tolerances.push_back (db::coord_traits <db::Coord>::rounded (t / dbu));
    }

    std::sort (tolerances.begin (), tolerances.end ());
    tolerances.erase (std::unique (tolerances.begin (), tolerances.end ()), tolerances.end ());

    if (tolerances.size () == 0) {
      tolerances.push_back (0);
    }
  }

  //  Create a map of new layers for original ones 
  std::map <db::LayerProperties, db::LayerProperties> new_layer_props;
  for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::iterator lm = layers.begin (); lm != layers.end (); ++lm) {
    new_layer_props.insert (std::make_pair (lm->first, lm->first));
  }

  double tile_size = 0; // in micron units
  bool tile_heal = mp_ui->heal_cb->isChecked ();

  if (! deep) {
    std::string text (tl::to_string (mp_ui->tiling->text ()));
    tl::Extractor ex (text.c_str ());
    double t = 0.0;
    if (ex.try_read (t)) {
      tile_size = t;
      if (tile_size < 1.0) {
        throw tl::Exception (tl::to_string (QObject::tr ("Invalid tile size (smaller than 1 micron or negative)")));  
      }
    }
  }
  
  std::string srca = cva->name () + ", Cell " + cva->layout ().cell_name (cva.cell_index ());
  std::string srcb = cvb->name () + ", Cell " + cvb->layout ().cell_name (cvb.cell_index ());

  //  Create the report database or identify the output layout
  rdb::Database *rdb = 0;
  rdb::Cell *rdb_cell = 0;
  int rdb_index = 0;

  int output_cv = -1;
  db::Layout *output_layout = 0;
  db::Cell *output_cell = 0;
  std::vector <unsigned int> output_layers;

  if (output_mode == OMMarkerDatabase) {

    rdb = new rdb::Database ();
    rdb->set_name ("XOR");
    rdb->set_top_cell_name (cva->layout ().cell_name (cva.cell_index ()));
    rdb_cell = rdb->create_cell (rdb->top_cell_name ());

    rdb_index = mp_view->add_rdb (rdb);

    rdb->set_description ("Comparison of '" + srca + "' vs. '" + srcb + "'");

  } else if (output_mode == OMNewLayout) {

    output_cv = mp_view->create_layout (true);
    output_layout = &mp_view->cellview (output_cv)->layout ();
    output_layout->dbu (dbu);

    for (std::map <db::LayerProperties, db::LayerProperties>::const_iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
      output_layers.push_back (output_layout->insert_layer (lp->second));
      lay::LayerProperties lay_lp;
      lay_lp.set_source (lay::ParsedLayerSource (lp->second, output_cv));
      mp_view->init_layer_properties (lay_lp);
      mp_view->insert_layer (mp_view->end_layers (), lay_lp);
    }

  } else if (output_mode == OMNewLayersA) {

    output_cv = cv_index_a;
    output_layout = &cva->layout ();
    output_cell = cva.cell ();

  } else if (output_mode == OMNewLayersB) {

    output_cv = cv_index_b;
    output_layout = &cvb->layout ();
    output_cell = cvb.cell ();

  }

  //  Clear undo buffers if layout is created.
  if (output_layout) {
    mp_view->manager ()->clear ();
  }

  std::vector<db::DBox> boxes; 

  db::DBox overall_box = (db::DBox (cva.cell ()->bbox ()) * cva->layout ().dbu ()) + (db::DBox (cvb.cell ()->bbox ()) * cvb->layout ().dbu ());

  if (region_mode == RMVisible) {
    overall_box &= mp_view->viewport ().box ();
    boxes.push_back (overall_box);
  } else if (region_mode == RMRulers) {
    ant::Service *ant_service = mp_view->get_plugin <ant::Service> ();
    if (ant_service) {
      ant::AnnotationIterator ant = ant_service->begin_annotations ();
      while (! ant.at_end ()) {
        boxes.push_back (overall_box & db::DBox (ant->p1 (), ant->p2 ()));
        ++ant;
      }
    }
  } else {
    boxes.push_back (overall_box);
  }

  bool was_cancelled = false;
  for (int mode = 0; mode < 3 && ! was_cancelled; ++mode) {

    rdb::Category *cat = 0; 
    db::BooleanOp::BoolOp op;
    std::string op_name;
    std::string op_desc;

    if (mode == 0 && axorb) {
      op = db::BooleanOp::Xor;
      op_name = "XOR";
      op_desc = "XOR between '" + srca + "' (Layout A) and '" + srcb + "' (Layout B)";
    } else if (mode == 1 && anotb) {
      op = db::BooleanOp::ANotB;
      op_name = "ANOTB";
      op_desc = "Geometry in '" + srca + "' (Layout A) but not in '" + srcb + "' (Layout B)";
    } else if (mode == 2 && bnota) {
      op = db::BooleanOp::BNotA;
      op_name = "BNOTA";
      op_desc = "Geometry in '" + srca + "' (Layout B) but not in '" + srcb + "' (Layout A)";
    } else {
      continue;
    }

    if (output_mode == OMMarkerDatabase) {
      cat = rdb->create_category (op_name);
      cat->set_description (op_desc);
    } else if (output_mode == OMNewLayout) {
      output_cell = &output_layout->cell (output_layout->add_cell (op_name.c_str ()));
    }

    std::vector <rdb::Category *> sub_categories;
    std::vector <std::vector <rdb::Category *> > layer_categories;
    std::vector <db::Cell *> sub_cells;
    std::vector <std::vector <unsigned int> > sub_output_layers;

    if (output_mode == OMMarkerDatabase) {

      //  create the categories for database output

      if (tolerances.size () == 1) {

        sub_categories.push_back (cat);

      } else {

        for (std::vector <db::Coord>::const_iterator t = tolerances.begin (); t != tolerances.end (); ++t) {

          rdb::Category *subcat;
          subcat = rdb->create_category (cat, tl::sprintf ("Tol_%g", *t * dbu));
          subcat->set_description (tl::sprintf ("XOR tolerance (min width reported): %g um", *t * dbu));
          sub_categories.push_back (subcat);

        }

      }

      layer_categories.reserve (sub_categories.size ());
      for (size_t i = 0; i < sub_categories.size (); ++i) {

        layer_categories.push_back (std::vector <rdb::Category *> ());

        for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::const_iterator l = layers.begin (); l != layers.end (); ++l) {

          rdb::Category *layercat = rdb->create_category (sub_categories [i], l->first.to_string ());
          layercat->set_description ("Results for layer " + l->first.to_string ());
          layer_categories.back ().push_back (layercat);

        }

      }

    } else if (output_mode == OMNewLayout) {

      if (tolerances.size () == 1) {

        sub_cells.push_back (output_cell);
        sub_output_layers.push_back (output_layers);

      } else {

        for (std::vector <db::Coord>::const_iterator t = tolerances.begin (); t != tolerances.end (); ++t) {

          sub_cells.push_back (&output_layout->cell (output_layout->add_cell (tl::sprintf ("%s_TOL_%g", op_name, *t * dbu).c_str ())));
          output_cell->insert (db::CellInstArray (db::CellInst (sub_cells.back ()->cell_index ()), db::Trans ()));

          sub_output_layers.push_back (output_layers);

        }

      }

    } else if (output_mode == OMNewLayersA || output_mode == OMNewLayersB) {

      if (tolerances.size () == 1) {

        db::LayerOffset o = layer_offset;
        if (! o.is_named ()) {
          o.name = "*_" + op_desc; // "_XOR" postfix by default
        }

        for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
          if (lp->first.is_named ()) {
            lp->second.name = lp->first.name;
          }
          lp->second += o;
        }

        output_layers.clear ();
        for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
          output_layers.push_back (output_layout->insert_layer (lp->second));
          lay::LayerProperties lay_lp;
          lay_lp.set_source (lay::ParsedLayerSource (lp->second, output_cv));
          mp_view->init_layer_properties (lay_lp);
          mp_view->insert_layer (mp_view->end_layers (), lay_lp);
        }

        sub_cells.push_back (output_cell);
        sub_output_layers.push_back (output_layers);

      } else {

        for (std::vector <db::Coord>::const_iterator t = tolerances.begin (); t != tolerances.end (); ++t) {

          db::LayerOffset o = layer_offset;
          if (! o.is_named ()) {
            o.name = "*_" + op_desc + tl::sprintf ("_T%d", int (t - tolerances.begin ()) + 1); // "_XOR" postfix by default
          }

          for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
            if (lp->first.is_named ()) {
              lp->second.name = lp->first.name;
            }
            lp->second += o;
          }

          output_layers.clear ();
          for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
            output_layers.push_back (output_layout->insert_layer (lp->second));
            lay::LayerProperties lay_lp;
            lay_lp.set_source (lay::ParsedLayerSource (lp->second, output_cv));
            mp_view->init_layer_properties (lay_lp);
            mp_view->insert_layer (mp_view->end_layers (), lay_lp);
          }

          sub_cells.push_back (output_cell);
          sub_output_layers.push_back (output_layers);

        }

      }

    }

    size_t todo_count = 0;
    XORJob::EmptyLayerHandling el_handling = XORJob::EL_optimize;
    if (summarize && output_mode == OMMarkerDatabase) {
      el_handling = XORJob::EL_summarize;
    } else if (process_el) {
      el_handling = XORJob::EL_process;
    }
    XORJob job (nworkers, output_mode, op, el_handling, dbu, cva, cvb, tolerances, sub_categories, layer_categories, sub_cells, sub_output_layers, rdb, rdb_cell);

    //  NOTE: uses min of both DBUs (see issue #1743)
    double common_dbu = std::min (cva->layout ().dbu (), cvb->layout ().dbu ());

    for (std::vector<db::DBox>::const_iterator b = boxes.begin (); b != boxes.end (); ++b) {

      db::DBox box (tl::round_down (b->left (), common_dbu), tl::round_down (b->bottom (), common_dbu),
                    tl::round_up (b->right (), common_dbu), tl::round_up (b->top (), common_dbu));

      //  compute the tiles if required
      db::Box box_a, box_b, box_out;
      db::Coord box_width_a = 0, box_height_a = 0, box_width_out = 0;
      db::Coord box_width_b = 0, box_height_b = 0, box_height_out = 0;

      size_t ntiles_w = 1, ntiles_h = 1;
      if (box.empty ()) {

        ntiles_w = ntiles_h = 0;

      } else {

        box_a = db::Box (box * (1.0 / cva->layout ().dbu ()));
        box_b = db::Box (box * (1.0 / cvb->layout ().dbu ()));
        box_out = db::Box (box * (1.0 / dbu));

        if (tile_size > 0.0) {

          ntiles_w = std::max (size_t (1), size_t (floor (box.width () / tile_size + 0.5)));
          ntiles_h = std::max (size_t (1), size_t (floor (box.height () / tile_size + 0.5)));

          double box_width = tl::round_up (box.width () / ntiles_w, common_dbu);
          double box_height = tl::round_up (box.height () / ntiles_h, common_dbu);

          box_width_a  = db::coord_traits<db::Coord>::rounded (box_width / cva->layout ().dbu ());
          box_height_a  = db::coord_traits<db::Coord>::rounded (box_height / cva->layout ().dbu ());

          box_width_b  = db::coord_traits<db::Coord>::rounded (box_width / cvb->layout ().dbu ());
          box_height_b  = db::coord_traits<db::Coord>::rounded (box_height / cvb->layout ().dbu ());

          box_width_out  = db::coord_traits<db::Coord>::rounded (box_width / dbu);
          box_height_out  = db::coord_traits<db::Coord>::rounded (box_height / dbu);

        }

      }

      //  Enlarge the tiles by half the maximum tolerance
      db::Coord tile_enlargement = 0;
      for (std::vector <db::Coord>::iterator t = tolerances.begin (); t != tolerances.end (); ++t) {
        db::Coord enlargement = (*t + 1) / 2; // round up
        if (enlargement > tile_enlargement) {
          tile_enlargement = enlargement;
        }
      }

      db::Coord tile_enlargement_a = db::coord_traits<db::Coord>::rounded_up (tile_enlargement * dbu / cva->layout ().dbu ());
      db::Coord tile_enlargement_b = db::coord_traits<db::Coord>::rounded_up (tile_enlargement * dbu / cvb->layout ().dbu ());

      if (ntiles_w > 1 || ntiles_h > 1 || region_mode != RMAll /*enforces clip*/) {
        job.set_tiles (true, int (ntiles_w), int (ntiles_h), tile_heal);
      }

      //  create the XOR tasks
      for (size_t nw = 0; nw < ntiles_w; ++nw) {

        for (size_t nh = 0; nh < ntiles_h; ++nh) {

          db::Box clip_box (box_out.left () + db::Coord (nw * box_width_out),
                            box_out.bottom () + db::Coord (nh * box_height_out),
                            (nw == ntiles_w - 1) ? box_out.right () : box_out.left () + db::Coord ((nw + 1) * box_width_out),
                            (nh == ntiles_h - 1) ? box_out.top () : box_out.bottom () + db::Coord ((nh + 1) * box_height_out));

          db::Box region_a (box_a.left () + db::Coord (nw * box_width_a),
                            box_a.bottom () + db::Coord (nh * box_height_a),
                            (nw == ntiles_w - 1) ? box_a.right () : box_a.left () + db::Coord ((nw + 1) * box_width_a),
                            (nh == ntiles_h - 1) ? box_a.top () : box_a.bottom () + db::Coord ((nh + 1) * box_height_a));

          db::Box region_b (box_b.left () + db::Coord (nw * box_width_b),
                            box_b.bottom () + db::Coord (nh * box_height_b),
                            (nw == ntiles_w - 1) ? box_b.right () : box_b.left () + db::Coord ((nw + 1) * box_width_b),
                            (nh == ntiles_h - 1) ? box_b.top () : box_b.bottom () + db::Coord ((nh + 1) * box_height_b));

          region_a.enlarge (db::Vector (tile_enlargement_a, tile_enlargement_a));
          region_b.enlarge (db::Vector (tile_enlargement_b, tile_enlargement_b));

          std::string tile_desc = tl::sprintf ("%d/%d,%d/%d", int (nw + 1), ntiles_w, int (nh + 1), ntiles_h);

          unsigned int layer_index = 0;
          for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::const_iterator l = layers.begin (); l != layers.end (); ++l, ++layer_index) {
            job.schedule (new XORTask (deep, tile_desc, clip_box, region_a, region_b, layer_index, l->first, l->second.first, l->second.second, int (nw), int (nh)));
          }

        }

      }

      todo_count += ntiles_w * ntiles_h * tolerances.size () * layers.size ();

    }

    bool was_cancelled = false;

    if (todo_count > 0) {

      tl::SelfTimer timer_tot (tl::verbosity () >= 11, "Total boolean time");

      //  TODO: there should be a general scheme of how thread-specific progress is merged
      //  into a global one ..
      XORProgress progress (tl::to_string (QObject::tr ("Performing ")) + op_name, todo_count, 1);

      //  We need to lock the layouts during the processing - in OMNewLayerA and OMNewLayerB mode
      //  we actually modify the layout we iterate over
      db::LayoutLocker locker_a (& cva->layout ());
      db::LayoutLocker locker_b (& cvb->layout ());

      try {

        job.start ();
        while (job.is_running ()) {
          //  This may throw an exception, if the cancel button has been pressed.
          job.update_progress (progress);
          job.wait (100);
        }

      } catch (tl::BreakException & /*ex*/) {
        job.terminate ();
        was_cancelled = true;
      } catch (tl::Exception &ex) {
        job.terminate ();
        throw ex;
      }

      if (job.has_error ()) {
        if (mp_view && output_mode == OMMarkerDatabase) {
          mp_view->remove_rdb (rdb_index);
        }
        throw tl::Exception (tl::to_string (QObject::tr ("Errors occurred during processing. First error message says:\n")) + job.error_messages ().front ());
      }

      //  apply healing if required
      job.finish ();

    }

    if (was_cancelled && output_mode == OMMarkerDatabase) {
      //  If the output mode is database, ask whether to keep the data collected so far.
      //  If the answer is yes, remove the RDB.
      //  Don't ask if the application has exit (window was closed)
      if (lay::ApplicationBase::instance ()->main_window () && !lay::ApplicationBase::instance ()->main_window ()->exited ()) {
        QMessageBox msgbox (QMessageBox::Question,
                            QObject::tr ("Keep Data For Cancelled Job"),
                            QObject::tr ("The job has been cancelled. Keep the data collected so far?"),
                            QMessageBox::Yes | QMessageBox::No);
        if (msgbox.exec () == QMessageBox::No) {
          if (mp_view) {
            mp_view->remove_rdb (rdb_index);
          }
          output_mode = OMNone;
        }
      }
    }

  }

  if (mp_view) {

    if (output_mode == OMMarkerDatabase) {
      mp_view->open_rdb_browser (rdb_index, cv_index_a);
    }

    mp_view->update_content ();

    if (output_mode == OMNewLayout && output_cell != 0 && output_cv >= 0) {
      mp_view->select_cell (output_cell->cell_index (), output_cv);
    }
  }
}

}

