
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2025 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 "dbAsIfFlatRegion.h"
#include "dbFlatRegion.h"
#include "dbFlatEdgePairs.h"
#include "dbFlatEdges.h"
#include "dbFlatTexts.h"
#include "dbEmptyRegion.h"
#include "dbEmptyEdgePairs.h"
#include "dbEmptyEdges.h"
#include "dbRegion.h"
#include "dbRegionUtils.h"
#include "dbShapeProcessor.h"
#include "dbBoxConvert.h"
#include "dbBoxScanner.h"
#include "dbClip.h"
#include "dbPolygonTools.h"
#include "dbHash.h"
#include "dbRegionLocalOperations.h"
#include "dbHierProcessor.h"
#include "dbCompoundOperation.h"
#include "dbLayoutToNetlist.h"

#include <sstream>

namespace db
{

namespace {

struct ResultCountingInserter
{
  typedef db::Polygon value_type;

  ResultCountingInserter (std::unordered_map<const db::Polygon *, size_t, tl::ptr_hash_from_value<db::Polygon> > &result)
    : mp_result (&result)
  {
    //  .. nothing yet ..
  }

  void insert (const db::Polygon &p)
  {
    (*mp_result)[&p] += 1;
  }

  void init (const db::Polygon *p)
  {
    (*mp_result)[p] = 0;
  }

private:
  std::unordered_map<const db::Polygon *, size_t, tl::ptr_hash_from_value<db::Polygon> > *mp_result;
};

}

static
RegionDelegate *region_from_box (const db::Box &b, db::properties_id_type prop_id)
{
  if (! b.empty () && b.width () > 0 && b.height () > 0) {
    FlatRegion *new_region = new FlatRegion ();
    if (prop_id != 0) {
      new_region->insert (db::BoxWithProperties (b, prop_id));
    } else {
      new_region->insert (b);
    }
    return new_region;
  } else {
    return new EmptyRegion ();
  }
}

// -------------------------------------------------------------------------------------------------------------
//  AsIfFlagRegion implementation

AsIfFlatRegion::AsIfFlatRegion ()
  : RegionDelegate (), m_bbox_valid (false)
{
  //  .. nothing yet ..
}

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

AsIfFlatRegion::AsIfFlatRegion (const AsIfFlatRegion &other)
  : RegionDelegate (other), m_bbox_valid (false)
{
  operator= (other);
}

AsIfFlatRegion &
AsIfFlatRegion::operator= (const AsIfFlatRegion &other)
{
  if (this != &other) {
    m_bbox_valid = other.m_bbox_valid;
    m_bbox = other.m_bbox;
  }

  return *this;
}

std::string
AsIfFlatRegion::to_string (size_t nmax) const
{
  std::ostringstream os;
  RegionIterator p (begin ());
  bool first = true;
  for ( ; ! p.at_end () && nmax != 0; ++p, --nmax) {
    if (! first) {
      os << ";";
    }
    first = false;
    os << p->to_string ();
    if (p.prop_id () != 0) {
      os << db::properties (p.prop_id ()).to_dict_var ().to_string ();
    }
  }
  if (! p.at_end ()) {
    os << "...";
  }
  return os.str ();
}

EdgesDelegate *
AsIfFlatRegion::edges (const EdgeFilterBase *filter, const PolygonToEdgeProcessorBase *proc) const
{
  std::unique_ptr<FlatEdges> result (new FlatEdges ());

  size_t n = 0;
  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    n += p->vertices ();
  }
  result->reserve (n);

  std::vector<db::EdgeWithProperties> heap;

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {

    if (proc) {

      heap.clear ();
      proc->process (p.wp (), heap);

      for (auto e = heap.begin (); e != heap.end (); ++e) {
        if (! filter || filter->selected (*e, e->properties_id ())) {
          if (e->properties_id () != 0) {
            result->insert (*e);
          } else {
            result->insert (e->base ());
          }
        }
      }

    } else {

      auto prop_id = p.prop_id ();

      for (db::Polygon::polygon_edge_iterator e = p->begin_edge (); ! e.at_end (); ++e) {
        if (! filter || filter->selected (*e, prop_id)) {
          if (prop_id != 0) {
            result->insert (db::EdgeWithProperties (*e, prop_id));
          } else {
            result->insert (*e);
          }
        }
      }

    }

  }

  return result.release ();
}

bool
AsIfFlatRegion::is_box () const
{
  RegionIterator p (begin ());
  if (p.at_end ()) {
    return false;
  } else {
    const db::Polygon &poly = *p;
    ++p;
    if (! p.at_end ()) {
      return false;
    } else {
      return poly.is_box ();
    }
  }
}

AsIfFlatRegion::area_type
AsIfFlatRegion::area (const db::Box &box) const
{
  area_type a = 0;

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    if (box.empty () || p->box ().inside (box)) {
      a += p->area ();
    } else if (p->is_box ()) {
      a += (p->box () & box).area ();
    } else {
      std::vector<db::Polygon> clipped;
      clip_poly (*p, box, clipped);
      for (std::vector<db::Polygon>::const_iterator c = clipped.begin (); c != clipped.end (); ++c) {
        a += c->area ();
      }
    }
  }

  return a;
}

AsIfFlatRegion::perimeter_type
AsIfFlatRegion::perimeter (const db::Box &box) const
{
  perimeter_type d = 0;

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {

    if (box.empty () || p->box ().inside (box)) {
      d += p->perimeter ();
    } else {

      for (db::Polygon::polygon_edge_iterator e = p->begin_edge (); ! e.at_end (); ++e) {

        if (box.empty ()) {
          d += (*e).length ();
        } else {

          std::pair<bool, db::Edge> ce = (*e).clipped (box);
          if (ce.first) {

            db::Coord dx = ce.second.dx ();
            db::Coord dy = ce.second.dy ();
            db::Coord x = ce.second.p1 ().x ();
            db::Coord y = ce.second.p1 ().y ();
            if ((dx == 0 && x == box.left ()   && dy < 0) ||
                (dx == 0 && x == box.right ()  && dy > 0) ||
                (dy == 0 && y == box.top ()    && dx < 0) ||
                (dy == 0 && y == box.bottom () && dx > 0)) {
              //  not counted -> box is at outside side of the edge
            } else {
              d += ce.second.length ();
            }

          }

        }

      }

    }

  }

  return d;
}

Box AsIfFlatRegion::bbox () const
{
  if (! m_bbox_valid) {
    m_bbox = compute_bbox ();
    m_bbox_valid = true;
  }
  return m_bbox;
}

Box AsIfFlatRegion::compute_bbox () const
{
  db::Box b;
  for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
    b += p->box ();
  }
  return b;
}

void AsIfFlatRegion::update_bbox (const db::Box &b)
{
  m_bbox = b;
  m_bbox_valid = true;
}

void AsIfFlatRegion::invalidate_bbox ()
{
  m_bbox_valid = false;
}

namespace
{

struct ComparePolygonsWithProperties
{
  bool operator() (const std::pair<db::properties_id_type, const db::Polygon *> &a, const std::pair<db::properties_id_type, const db::Polygon *> &b) const
  {
    if (a.first != b.first) {
      return db::properties_id_less (a.first, b.first);
    }
    return *a.second < *b.second;
  }
};

}

void AsIfFlatRegion::merge_polygons_to (db::Shapes &output, bool min_coherence, unsigned int min_wc, bool join_properties_on_merge) const
{
  db::EdgeProcessor ep (report_progress (), progress_desc ());
  ep.set_base_verbosity (base_verbosity ());

  //  count edges and reserve memory
  size_t n = 0;
  db::properties_id_type prop_id = 0;
  bool multiple_properties = false;
  for (RegionIterator s (begin ()); ! s.at_end (); ++s, ++n) {
    if (n == 0) {
      prop_id = s.prop_id ();
    } else if (! multiple_properties && prop_id != s.prop_id ()) {
      multiple_properties = true;
    }
  }

  if (multiple_properties && join_properties_on_merge) {

    //  this merge variant requires a two-step approach: we first and then join original properties IDs
    //  in a separate interaction step

    std::vector<db::properties_id_type> org_prop_ids;
    org_prop_ids.reserve (n);

    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    size_t org_poly_id = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p, ++org_poly_id) {
      org_prop_ids.push_back (p.prop_id ());
      ep.insert (*p, org_poly_id);
    }

    //  and run the merge step
    db::MergeOp op (min_wc);
    db::PolygonContainer pc;
    db::PolygonGenerator pg (pc, false /*don't resolve holes*/, min_coherence);
    ep.process (pg, op);

    //  reserve space for new (merged) polygons
    for (auto p = pc.polygons ().begin (); p != pc.polygons ().end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    size_t merged_poly_id = org_poly_id;
    for (auto p = pc.polygons ().begin (); p != pc.polygons ().end (); ++p, ++merged_poly_id) {
      ep.insert (*p, merged_poly_id);
    }

    //  compute interactions between merged and original polygons

    db::InteractionDetector id;
    id.set_include_touching (false);
    db::EdgeSink es;
    ep.process (es, id);
    id.finish ();

    //  collect original property IDs per merged polygon

    std::vector<std::set<db::properties_id_type> > prop_ids_per_merged_polygon;
    prop_ids_per_merged_polygon.resize (merged_poly_id - org_poly_id);

    for (auto ii = id.begin (); ii != id.end (); ++ii) {
      auto first = std::min (ii->first, ii->second);
      auto second = std::max (ii->first, ii->second);
      if (first < org_poly_id && second >= org_poly_id) {
        prop_ids_per_merged_polygon [second - org_poly_id].insert (org_prop_ids [first]);
      }
    }

    //  Form new polygons with joined properties

    for (auto p = pc.polygons ().begin (); p != pc.polygons ().end (); ++p) {

      const std::set<db::properties_id_type> &prop_ids = prop_ids_per_merged_polygon [p - pc.polygons ().begin ()];

      db::properties_id_type prop_id = 0;
      if (prop_ids.size () == 1) {
        prop_id = *prop_ids.begin ();
      } else if (prop_ids.size () > 1) {
        db::PropertiesSet ps;
        for (auto p = prop_ids.begin (); p != prop_ids.end (); ++p) {
          //  merge in "larger one wins" mode - the advantage of this mode is that
          //  it is independent on the order of the attribute sets (which in fact are pointers)
          ps.join_max (db::properties (*p));
        }
        prop_id = db::properties_id (ps);
      }

      if (prop_id != 0) {
        output.insert (db::PolygonWithProperties (*p, prop_id));
      } else {
        output.insert (*p);
      }

    }

  } else if (multiple_properties) {

    db::Shapes result (output.is_editable ());

    std::vector<std::pair<db::properties_id_type, const db::Polygon *> > polygons_by_prop_id;
    polygons_by_prop_id.reserve (n);

    db::AddressablePolygonDelivery addressable_polygons (begin ());
    while (! addressable_polygons.at_end ()) {
      polygons_by_prop_id.push_back (std::make_pair (addressable_polygons.prop_id (), addressable_polygons.operator-> ()));
      addressable_polygons.inc ();
    }

    std::sort (polygons_by_prop_id.begin (), polygons_by_prop_id.end (), ComparePolygonsWithProperties ());

    for (auto p = polygons_by_prop_id.begin (); p != polygons_by_prop_id.end (); ) {

      auto pp = p;
      while (pp != polygons_by_prop_id.end () && pp->first == p->first) {
        ++pp;
      }

      ep.clear ();

      n = 0;
      for (auto i = p; i != pp; ++i) {
        n += i->second->vertices ();
      }
      ep.reserve (n);

      n = 0;
      for (auto i = p; i != pp; ++i, ++n) {
        ep.insert (*i->second, n);
      }

      //  and run the merge step
      db::MergeOp op (min_wc);
      db::ShapeGenerator pc (result, false /*don't clear*/, p->first);
      db::PolygonGenerator pg (pc, false /*don't resolve holes*/, min_coherence);
      ep.process (pg, op);

      p = pp;

    }

    output.swap (result);

  } else {

    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    //  insert the polygons into the processor
    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p, ++n) {
      ep.insert (*p, n);
    }

    output.clear ();

    //  and run the merge step
    db::MergeOp op (min_wc);
    db::ShapeGenerator pc (output, false /*don't clear*/, prop_id);
    db::PolygonGenerator pg (pc, false /*don't resolve holes*/, min_coherence);
    ep.process (pg, op);

  }
}

RegionDelegate *
AsIfFlatRegion::filtered (const PolygonFilterBase &filter) const
{
  std::unique_ptr<FlatRegion> new_region (new FlatRegion ());

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    if (filter.selected (*p, p.prop_id ())) {
      if (p.prop_id () != 0) {
        new_region->insert (db::PolygonWithProperties (*p, p.prop_id ()));
      } else {
        new_region->insert (*p);
      }
    }
  }

  new_region->set_is_merged (true);
  return new_region.release ();
}

std::pair<RegionDelegate *, RegionDelegate *>
AsIfFlatRegion::filtered_pair (const PolygonFilterBase &filter) const
{
  std::unique_ptr<FlatRegion> new_region_true (new FlatRegion ());
  std::unique_ptr<FlatRegion> new_region_false (new FlatRegion ());

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    if (filter.selected (*p, p.prop_id ())) {
      if (p.prop_id () != 0) {
        new_region_true->insert (db::PolygonWithProperties (*p, p.prop_id ()));
      } else {
        new_region_true->insert (*p);
      }
    } else {
      if (p.prop_id () != 0) {
        new_region_false->insert (db::PolygonWithProperties (*p, p.prop_id ()));
      } else {
        new_region_false->insert (*p);
      }
    }
  }

  new_region_true->set_is_merged (true);
  new_region_false->set_is_merged (true);
  return std::make_pair (new_region_true.release (), new_region_false.release ());
}

RegionDelegate *
AsIfFlatRegion::processed (const PolygonProcessorBase &filter) const
{
  std::unique_ptr<FlatRegion> new_region (new FlatRegion ());
  if (filter.result_must_not_be_merged ()) {
    new_region->set_merged_semantics (false);
  }

  std::vector<db::PolygonWithProperties> poly_res;

  for (RegionIterator p (filter.requires_raw_input () ? begin () : begin_merged ()); ! p.at_end (); ++p) {

    poly_res.clear ();
    filter.process (p.wp (), poly_res);
    for (auto pr = poly_res.begin (); pr != poly_res.end (); ++pr) {
      if (pr->properties_id () != 0) {
        new_region->insert (*pr);
      } else {
        new_region->insert (pr->base ());
      }
    }

  }

  return new_region.release ();
}

EdgesDelegate *
AsIfFlatRegion::processed_to_edges (const PolygonToEdgeProcessorBase &filter) const
{
  std::unique_ptr<FlatEdges> new_edges (new FlatEdges ());
  if (filter.result_must_not_be_merged ()) {
    new_edges->set_merged_semantics (false);
  }

  std::vector<db::EdgeWithProperties> edge_res;

  for (RegionIterator p (filter.requires_raw_input () ? begin () : begin_merged ()); ! p.at_end (); ++p) {

    edge_res.clear ();
    filter.process (p.wp (), edge_res);
    for (auto er = edge_res.begin (); er != edge_res.end (); ++er) {
      if (er->properties_id () != 0) {
        new_edges->insert (*er);
      } else {
        new_edges->insert (er->base ());
      }
    }

  }

  return new_edges.release ();
}

EdgePairsDelegate *
AsIfFlatRegion::processed_to_edge_pairs (const PolygonToEdgePairProcessorBase &filter) const
{
  std::unique_ptr<FlatEdgePairs> new_edge_pairs (new FlatEdgePairs ());
  if (filter.result_must_not_be_merged ()) {
    new_edge_pairs->set_merged_semantics (false);
  }

  std::vector<db::EdgePairWithProperties> edge_pair_res;

  for (RegionIterator p (filter.requires_raw_input () ? begin () : begin_merged ()); ! p.at_end (); ++p) {

    edge_pair_res.clear ();
    filter.process (p.wp (), edge_pair_res);
    for (auto epr = edge_pair_res.begin (); epr != edge_pair_res.end (); ++epr) {
      if (epr->properties_id () != 0) {
        new_edge_pairs->insert (*epr);
      } else {
        new_edge_pairs->insert (epr->base ());
      }
    }

  }

  return new_edge_pairs.release ();
}

namespace {

class OutputPairHolder
{
public:
  OutputPairHolder (InteractingOutputMode output_mode, bool merged_semantics)
  {
    if (output_mode == None) {
      return;
    }

    if (output_mode == Positive || output_mode == Negative || output_mode == PositiveAndNegative) {
      m_positive.reset (new FlatRegion (merged_semantics));
      m_results.push_back (& m_positive->raw_polygons ());
    } else {
      m_results.push_back ((db::Shapes *) 0);
    }

    if (output_mode == PositiveAndNegative) {
      m_negative.reset (new FlatRegion (merged_semantics));
      m_results.push_back (& m_negative->raw_polygons ());
    }
  }

  std::pair<RegionDelegate *, RegionDelegate *> region_pair ()
  {
    return std::make_pair (m_positive.release (), m_negative.release ());
  }

  const std::vector<db::Shapes *> &results () { return m_results; }

private:
  std::unique_ptr<FlatRegion> m_positive, m_negative;
  std::vector<db::Shapes *> m_results;
};

}

std::pair<RegionDelegate *, RegionDelegate *>
AsIfFlatRegion::in_and_out_generic (const Region &other, InteractingOutputMode output_mode) const
{
  OutputPairHolder oph (output_mode, merged_semantics ());

  if (output_mode == None) {
    return oph.region_pair ();
  }

  //  shortcut
  if (empty ()) {
    if (output_mode == Positive || output_mode == Negative) {
      return std::make_pair (clone (), (RegionDelegate *) 0);
    } else {
      return std::make_pair (clone (), clone ());
    }
  } else if (other.empty ()) {
    if (output_mode == Positive) {
      return std::make_pair (new EmptyRegion (), (RegionDelegate *) 0);
    } else if (output_mode == Negative) {
      return std::make_pair (clone (), (RegionDelegate *) 0);
    } else {
      return std::make_pair (new EmptyRegion (), clone ());
    }
  }

  std::set <db::Polygon> op;
  for (RegionIterator o (other.begin_merged ()); ! o.at_end (); ++o) {
    op.insert (*o);
  }

  std::unique_ptr<FlatRegion> new_region (new FlatRegion (false));

  for (RegionIterator o (begin_merged ()); ! o.at_end (); ++o) {
    if (op.find (*o) != op.end ()) {
      if (output_mode == Positive || output_mode == PositiveAndNegative) {
        oph.results () [0]->insert (*o);
      }
    } else {
      if (output_mode == Negative) {
        oph.results () [0]->insert (*o);
      } else if (output_mode == PositiveAndNegative) {
        oph.results () [1]->insert (*o);
      }
    }
  }

  return oph.region_pair ();
}

std::pair<RegionDelegate *, RegionDelegate *>
AsIfFlatRegion::selected_interacting_generic (const Edges &other, InteractingOutputMode output_mode, size_t min_count, size_t max_count) const
{
  OutputPairHolder oph (output_mode, merged_semantics () || is_merged ());

  if (output_mode == None) {
    return oph.region_pair ();
  }

  min_count = std::max (size_t (1), min_count);

  //  shortcut
  if (empty ()) {
    if (output_mode == Positive || output_mode == Negative) {
      return std::make_pair (clone (), (RegionDelegate *) 0);
    } else {
      return std::make_pair (clone (), clone ());
    }
  } else if (max_count < min_count || other.empty ()) {
    if (output_mode == Positive) {
      return std::make_pair (new EmptyRegion (), (RegionDelegate *) 0);
    } else if (output_mode == Negative) {
      return std::make_pair (clone (), (RegionDelegate *) 0);
    } else {
      return std::make_pair (new EmptyRegion (), clone ());
    }
  }

  bool counting = !(min_count == 1 && max_count == std::numeric_limits<size_t>::max ());

  db::RegionIterator polygons (begin_merged ());

  db::interacting_with_edge_local_operation<db::Polygon, db::Edge, db::Polygon> op (output_mode, min_count, max_count, true);

  db::local_processor<db::Polygon, db::Edge, db::Polygon> proc;
  proc.set_base_verbosity (base_verbosity ());
  proc.set_description (progress_desc ());
  proc.set_report_progress (report_progress ());

  std::vector<generic_shape_iterator<db::Edge> > others;
  others.push_back (counting ? other.begin_merged () : other.begin ());

  std::unique_ptr<FlatRegion> output (new FlatRegion (merged_semantics ()));
  std::vector<db::Shapes *> results;
  results.push_back (&output->raw_polygons ());

  proc.run_flat (polygons, others, std::vector<bool> (), &op, oph.results ());

  return oph.region_pair ();
}

std::pair<RegionDelegate *, RegionDelegate *>
AsIfFlatRegion::selected_interacting_generic (const Texts &other, InteractingOutputMode output_mode, size_t min_count, size_t max_count) const
{
  OutputPairHolder oph (output_mode, merged_semantics () || is_merged ());

  if (output_mode == None) {
    return oph.region_pair ();
  }

  min_count = std::max (size_t (1), min_count);

  //  shortcut
  if (empty ()) {
    if (output_mode == Positive || output_mode == Negative) {
      return std::make_pair (clone (), (RegionDelegate *) 0);
    } else {
      return std::make_pair (clone (), clone ());
    }
  } else if (max_count < min_count || other.empty ()) {
    if (output_mode == Positive) {
      return std::make_pair (new EmptyRegion (), (RegionDelegate *) 0);
    } else if (output_mode == Negative) {
      return std::make_pair (clone (), (RegionDelegate *) 0);
    } else {
      return std::make_pair (new EmptyRegion (), clone ());
    }
  }

  db::RegionIterator polygons (begin_merged ());

  db::interacting_with_text_local_operation<db::Polygon, db::Text, db::Polygon> op (output_mode, min_count, max_count);

  db::local_processor<db::Polygon, db::Text, db::Polygon> proc;
  proc.set_base_verbosity (base_verbosity ());
  proc.set_description (progress_desc ());
  proc.set_report_progress (report_progress ());

  std::vector<generic_shape_iterator<db::Text> > others;
  others.push_back (other.begin ());

  proc.run_flat (polygons, others, std::vector<bool> (), &op, oph.results ());

  return oph.region_pair ();
}


std::pair<RegionDelegate *, RegionDelegate *>
AsIfFlatRegion::selected_interacting_generic (const Region &other, int mode, bool touching, InteractingOutputMode output_mode, size_t min_count, size_t max_count) const
{
  OutputPairHolder oph (output_mode, merged_semantics () || is_merged ());

  if (output_mode == None) {
    return oph.region_pair ();
  }

  min_count = std::max (size_t (1), min_count);

  //  shortcut
  if (empty ()) {
    if (output_mode == Positive || output_mode == Negative) {
      return std::make_pair (clone (), (RegionDelegate *) 0);
    } else {
      return std::make_pair (clone (), clone ());
    }
  } else if (max_count < min_count || other.empty ()) {
    //  clear, if b is empty and
    //   * mode is inside, enclosing or interacting and inverse is false ("inside" or "interacting")
    //   * mode is outside and inverse is true ("not outside")
    if ((mode <= 0)) {
      if (output_mode == Positive) {
        return std::make_pair (new EmptyRegion (), (RegionDelegate *) 0);
      } else if (output_mode == Negative) {
        return std::make_pair (clone (), (RegionDelegate *) 0);
      } else {
        return std::make_pair (new EmptyRegion (), clone ());
      }
    } else {
      if (output_mode == Positive) {
        return std::make_pair (clone(), (RegionDelegate *) 0);
      } else if (output_mode == Negative) {
        return std::make_pair (new EmptyRegion (), (RegionDelegate *) 0);
      } else {
        return std::make_pair (clone (), new EmptyRegion ());
      }
    }
  }

  bool counting = !(min_count == 1 && max_count == std::numeric_limits<size_t>::max ());

  db::RegionIterator polygons (begin_merged ());

  db::interacting_local_operation<db::Polygon, db::Polygon, db::Polygon> op (mode, touching, output_mode, min_count, max_count, true);

  db::local_processor<db::Polygon, db::Polygon, db::Polygon> proc;
  proc.set_base_verbosity (base_verbosity ());
  proc.set_description (progress_desc ());
  proc.set_report_progress (report_progress ());

  std::vector<generic_shape_iterator<db::Polygon> > others;
  //  NOTE: with counting the other region needs to be merged
  others.push_back (counting ? other.begin_merged () : other.begin ());

  proc.run_flat (polygons, others, std::vector<bool> (), &op, oph.results ());

  return oph.region_pair ();
}

EdgesDelegate *
AsIfFlatRegion::pull_generic (const Edges &other) const
{
  if (other.empty ()) {
    return other.delegate ()->clone ();
  } else if (empty ()) {
    return new EmptyEdges ();
  }

  db::RegionIterator polygons (begin ());

  db::pull_with_edge_local_operation <db::Polygon, db::Edge, db::Edge> op;

  db::local_processor<db::Polygon, db::Edge, db::Edge> proc;
  proc.set_base_verbosity (base_verbosity ());
  proc.set_description (progress_desc ());
  proc.set_report_progress (report_progress ());

  std::vector<generic_shape_iterator<db::Edge> > others;
  others.push_back (other.begin_merged ());

  std::unique_ptr<FlatEdges> output (new FlatEdges (other.merged_semantics () || other.is_merged ()));
  std::vector<db::Shapes *> results;
  results.push_back (&output->raw_edges ());

  proc.run_flat (polygons, others, std::vector<bool> (), &op, results);

  return output.release ();
}

TextsDelegate *
AsIfFlatRegion::pull_generic (const Texts &other) const
{
  db::RegionIterator polygons (begin ());

  db::pull_with_text_local_operation <db::Polygon, db::Text, db::Text> op;

  db::local_processor<db::Polygon, db::Text, db::Text> proc;
  proc.set_base_verbosity (base_verbosity ());
  proc.set_description (progress_desc ());
  proc.set_report_progress (report_progress ());

  std::vector<generic_shape_iterator<db::Text> > others;
  others.push_back (other.begin ());

  std::unique_ptr<FlatTexts> output (new FlatTexts ());
  std::vector<db::Shapes *> results;
  results.push_back (&output->raw_texts ());

  proc.run_flat (polygons, others, std::vector<bool> (), &op, results);

  return output.release ();
}

RegionDelegate *
AsIfFlatRegion::pull_generic (const Region &other, int mode, bool touching) const
{
  db::RegionIterator polygons (begin ());

  db::pull_local_operation <db::Polygon, db::Polygon, db::Polygon> op (mode, touching);

  db::local_processor<db::Polygon, db::Polygon, db::Polygon> proc;
  proc.set_base_verbosity (base_verbosity ());
  proc.set_description (progress_desc ());
  proc.set_report_progress (report_progress ());

  std::vector<generic_shape_iterator<db::Polygon> > others;
  others.push_back (other.begin_merged ());

  std::unique_ptr<FlatRegion> output (new FlatRegion (other.merged_semantics () || other.is_merged ()));
  std::vector<db::Shapes *> results;
  results.push_back (&output->raw_polygons ());

  proc.run_flat (polygons, others, std::vector<bool> (), &op, results);

  return output.release ();
}

template <class Trans>
void
AsIfFlatRegion::produce_markers_for_grid_check (const db::Polygon &poly, const Trans &tr, db::Coord gx, db::Coord gy, db::Shapes &shapes)
{
  Trans tr_inv = tr.inverted ();

  gx = std::max (db::Coord (1), gx);
  gy = std::max (db::Coord (1), gy);

  for (size_t i = 0; i < poly.holes () + 1; ++i) {

    db::Polygon::polygon_contour_iterator b, e;

    if (i == 0) {
      b = poly.begin_hull ();
      e = poly.end_hull ();
    } else {
      b = poly.begin_hole ((unsigned int) (i - 1));
      e = poly.end_hole ((unsigned int)  (i - 1));
    }

    for (db::Polygon::polygon_contour_iterator pt = b; pt != e; ++pt) {
      db::Point p = tr * *pt;
      if ((p.x () % gx) != 0 || (p.y () % gy) != 0) {
        shapes.insert (EdgePair (db::Edge (p, p), db::Edge (p, p)).transformed (tr_inv));
      }
    }

  }
}

template void AsIfFlatRegion::produce_markers_for_grid_check<db::ICplxTrans> (const db::Polygon &poly, const db::ICplxTrans &tr, db::Coord gx, db::Coord gy, db::Shapes &shapes);
template void AsIfFlatRegion::produce_markers_for_grid_check<db::UnitTrans> (const db::Polygon &poly, const db::UnitTrans &tr, db::Coord gx, db::Coord gy, db::Shapes &shapes);

EdgePairsDelegate *
AsIfFlatRegion::grid_check (db::Coord gx, db::Coord gy) const
{
  if (gx < 0 || gy < 0) {
    throw tl::Exception (tl::to_string (tr ("Grid check requires a positive grid value")));
  }

  if (gx == 0 && gy == 0) {
    return new EmptyEdgePairs ();
  }

  std::unique_ptr<db::FlatEdgePairs> res (new db::FlatEdgePairs ());

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    produce_markers_for_grid_check (*p, db::UnitTrans (), gx, gy, res->raw_edge_pairs ());
  }

  return res.release ();
}

static bool ac_less (double cos_a, bool gt180_a, double cos_b, bool gt180_b)
{
  if (gt180_a != gt180_b) {
    return gt180_a < gt180_b;
  } else {
    if (gt180_a) {
      return cos_a < cos_b - 1e-10;
    } else {
      return cos_a > cos_b + 1e-10;
    }
  }
}

template <class Trans>
void
AsIfFlatRegion::produce_markers_for_angle_check (const db::Polygon &poly, const Trans &tr, double min, double max, bool inverse, db::Shapes &shapes)
{
  double cos_min = cos (std::max (0.0, std::min (360.0, min)) / 180.0 * M_PI);
  double cos_max = cos (std::max (0.0, std::min (360.0, max)) / 180.0 * M_PI);
  bool gt180_min = min > 180.0;
  bool gt180_max = max > 180.0;

  for (size_t i = 0; i < poly.holes () + 1; ++i) {

    const db::Polygon::contour_type *h = 0;
    if (i == 0) {
      h = &poly.hull ();
    } else {
      h = &poly.hole ((unsigned int) (i - 1));
    }

    size_t np = h->size ();

    for (size_t j = 0; j < np; ++j) {

      db::Edge e ((*h) [j], (*h) [(j + 1) % np]);
      e.transform (tr);
      db::Edge ee (e.p2 (), (*h) [(j + 2) % np]);
      ee.transform (tr);

      double le = e.double_length ();
      double lee = ee.double_length ();

      double cos_a = -db::sprod (e, ee) / (le * lee);
      bool gt180_a = db::vprod_sign (e, ee) > 0;

      if ((ac_less (cos_a, gt180_a, cos_max, gt180_max) && !ac_less (cos_a, gt180_a, cos_min, gt180_min)) == !inverse) {
        shapes.insert (EdgePair (e, ee));
      }

    }

  }
}

template void AsIfFlatRegion::produce_markers_for_angle_check<db::ICplxTrans> (const db::Polygon &poly, const db::ICplxTrans &tr, double min, double max, bool inverse, db::Shapes &shapes);
template void AsIfFlatRegion::produce_markers_for_angle_check<db::UnitTrans> (const db::Polygon &poly, const db::UnitTrans &tr, double min, double max, bool inverse, db::Shapes &shapes);

EdgePairsDelegate *
AsIfFlatRegion::angle_check (double min, double max, bool inverse) const
{
  std::unique_ptr<db::FlatEdgePairs> res (new db::FlatEdgePairs ());

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    produce_markers_for_angle_check (*p, db::UnitTrans (), min, max, inverse, res->raw_edge_pairs ());
  }

  return res.release ();
}

RegionDelegate *
AsIfFlatRegion::snapped (db::Coord gx, db::Coord gy)
{
  if (gx < 0 || gy < 0) {
    throw tl::Exception (tl::to_string (tr ("Grid snap requires a positive grid value")));
  }

  std::unique_ptr<FlatRegion> new_region (new FlatRegion ());

  gx = std::max (db::Coord (1), gx);
  gy = std::max (db::Coord (1), gy);

  std::vector<db::Point> heap;

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    new_region->raw_polygons ().insert (snapped_polygon (*p, gx, gy, heap));
  }

  return new_region.release ();
}

RegionDelegate *
AsIfFlatRegion::scaled_and_snapped (db::Coord gx, db::Coord mx, db::Coord dx, db::Coord gy, db::Coord my, db::Coord dy)
{
  if (gx < 0 || gy < 0) {
    throw tl::Exception (tl::to_string (tr ("Grid snap requires a positive grid value")));
  }

  if (mx <= 0 || dx <= 0 || my <= 0 || dy <= 0) {
    throw tl::Exception (tl::to_string (tr ("Scale and snap requires positive and non-null magnification or divisor values")));
  }

  std::unique_ptr<FlatRegion> new_region (new FlatRegion (merged_semantics ()));

  gx = std::max (db::Coord (1), gx);
  gy = std::max (db::Coord (1), gy);

  std::vector<db::Point> heap;

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
    new_region->raw_polygons ().insert (scaled_and_snapped_polygon (*p, gx, mx, dx, 0, gy, my, dy, 0, heap));
  }

  return new_region.release ();
}

template <class TR>
static
void region_cop_with_properties_impl (AsIfFlatRegion *region, db::Shapes *output_to, db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
  db::local_processor<db::PolygonWithProperties, db::PolygonWithProperties, db::object_with_properties<TR> > proc;
  proc.set_base_verbosity (region->base_verbosity ());
  proc.set_description (region->progress_desc ());
  proc.set_report_progress (region->report_progress ());

  db::generic_shape_iterator<db::PolygonWithProperties> polygons (db::make_wp_iter (region->begin_merged ()));

  std::vector<generic_shape_iterator<db::PolygonWithProperties> > others;
  std::vector<bool> foreign;
  std::vector<db::Region *> inputs = node.inputs ();
  for (std::vector<db::Region *>::const_iterator i = inputs.begin (); i != inputs.end (); ++i) {
    if (*i == subject_regionptr () || *i == foreign_regionptr ()) {
      others.push_back (db::make_wp_iter (region->begin_merged ()));
      foreign.push_back (*i == foreign_regionptr ());
    } else {
      others.push_back (db::make_wp_iter ((*i)->begin ()));
      foreign.push_back (false);
    }
  }

  std::vector<db::Shapes *> results;
  results.push_back (output_to);

  compound_local_operation_with_properties<db::Polygon, db::Polygon, TR> op (&node, prop_constraint);
  proc.run_flat (polygons, others, foreign, &op, results);
}

EdgePairsDelegate *
AsIfFlatRegion::cop_to_edge_pairs (db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
  std::unique_ptr<FlatEdgePairs> output (new FlatEdgePairs ());
  region_cop_with_properties_impl<db::EdgePair> (this, &output->raw_edge_pairs (), node, prop_constraint);
  return output.release ();
}

RegionDelegate *
AsIfFlatRegion::cop_to_region (db::CompoundRegionOperationNode &node, db::PropertyConstraint prop_constraint)
{
  std::unique_ptr<FlatRegion> output (new FlatRegion ());
  region_cop_with_properties_impl<db::Polygon> (this, &output->raw_polygons (), node, prop_constraint);
  return output.release ();
}

EdgesDelegate *
AsIfFlatRegion::cop_to_edges (db::CompoundRegionOperationNode &node, PropertyConstraint prop_constraint)
{
  std::unique_ptr<FlatEdges> output (new FlatEdges ());
  region_cop_with_properties_impl<db::Edge> (this, &output->raw_edges (), node, prop_constraint);
  return output.release ();
}

EdgePairsDelegate *
AsIfFlatRegion::width_check (db::Coord d, const RegionCheckOptions &options) const
{
  return run_single_polygon_check (db::WidthRelation, d, options);
}

EdgePairsDelegate *
AsIfFlatRegion::space_or_isolated_check (db::Coord d, const RegionCheckOptions &options, bool isolated) const
{
  if (options.opposite_filter != NoOppositeFilter || options.rect_filter != NoRectFilter || options.shielded) {
    //  NOTE: we have to use the "foreign" scheme with a filter because only this scheme
    //  guarantees that all subject shapes are visited.
    return run_check (db::SpaceRelation, isolated, foreign_regionptr (), d, options);
  } else {
    return run_check (db::SpaceRelation, isolated, subject_regionptr (), d, options);
  }
}

EdgePairsDelegate *
AsIfFlatRegion::space_check (db::Coord d, const RegionCheckOptions &options) const
{
  return space_or_isolated_check (d, options, false);
}

EdgePairsDelegate *
AsIfFlatRegion::isolated_check (db::Coord d, const RegionCheckOptions &options) const
{
  return space_or_isolated_check (d, options, true);
}

EdgePairsDelegate *
AsIfFlatRegion::notch_check (db::Coord d, const RegionCheckOptions &options) const
{
  return run_single_polygon_check (db::SpaceRelation, d, options);
}

EdgePairsDelegate *
AsIfFlatRegion::enclosing_check (const Region &other, db::Coord d, const RegionCheckOptions &options) const
{
  return run_check (db::OverlapRelation, true, &other, d, options);
}

EdgePairsDelegate *
AsIfFlatRegion::overlap_check (const Region &other, db::Coord d, const RegionCheckOptions &options) const
{
  return run_check (db::WidthRelation, true, &other, d, options);
}

EdgePairsDelegate *
AsIfFlatRegion::separation_check (const Region &other, db::Coord d, const RegionCheckOptions &options) const
{
  return run_check (db::SpaceRelation, true, &other, d, options);
}

EdgePairsDelegate *
AsIfFlatRegion::inside_check (const Region &other, db::Coord d, const RegionCheckOptions &options) const
{
  return run_check (db::InsideRelation, true, &other, d, options);
}

EdgePairsDelegate *
AsIfFlatRegion::run_check (db::edge_relation_type rel, bool different_polygons, const Region *other, db::Coord d, const RegionCheckOptions &options) const
{
  //  force different polygons in the different properties case to skip intra-polygon checks
  if (pc_always_different (options.prop_constraint)) {
    different_polygons = true;
  }

  bool needs_merged_primary = different_polygons || options.needs_merged ();

  db::RegionIterator polygons (needs_merged_primary ? begin_merged () : begin ());
  bool primary_is_merged = ! merged_semantics () || needs_merged_primary || is_merged ();

  EdgeRelationFilter check (rel, d, options);

  std::vector<db::RegionIterator> others;
  std::vector<bool> foreign;
  bool has_other = false;
  bool other_is_merged = true;

  if (other == subject_regionptr () || other == foreign_regionptr ()) {
    foreign.push_back (other == foreign_regionptr ());
    others.push_back (polygons);
    other_is_merged = primary_is_merged;
  } else {
    foreign.push_back (false);
    if (! other->merged_semantics ()) {
      others.push_back (other->begin ());
      other_is_merged = true;
    } else if (options.whole_edges) {
      //  NOTE: whole edges needs both inputs merged
      others.push_back (other->begin_merged ());
      other_is_merged = true;
    } else {
      others.push_back (other->begin ());
      other_is_merged = other->is_merged ();
    }
    has_other = true;
  }

  std::unique_ptr<FlatEdgePairs> output (new FlatEdgePairs ());

  std::vector<db::Shapes *> results;
  results.push_back (&output->raw_edge_pairs ());

  if (pc_skip (options.prop_constraint)) {

    db::check_local_operation<db::Polygon, db::Polygon> op (check, different_polygons, primary_is_merged, has_other, other_is_merged, options);

    db::local_processor<db::Polygon, db::Polygon, db::EdgePair> proc;
    proc.set_base_verbosity (base_verbosity ());
    proc.set_description (progress_desc ());
    proc.set_report_progress (report_progress ());

    proc.run_flat (polygons, others, foreign, &op, results);

  } else {

    db::check_local_operation_with_properties<db::Polygon, db::Polygon> op (check, different_polygons, primary_is_merged, has_other, other_is_merged, options);

    db::local_processor<db::PolygonWithProperties, db::PolygonWithProperties, db::EdgePairWithProperties> proc;
    proc.set_base_verbosity (base_verbosity ());
    proc.set_description (progress_desc ());
    proc.set_report_progress (report_progress ());

    std::vector<db::generic_shape_iterator<db::PolygonWithProperties> > others_wp;
    for (auto o = others.begin (); o != others.end (); ++o) {
      others_wp.push_back (db::make_wp_iter (std::move (*o)));
    }

    proc.run_flat (db::make_wp_iter (std::move (polygons)), others_wp, foreign, &op, results);

  }

  return output.release ();
}

EdgePairsDelegate *
AsIfFlatRegion::run_single_polygon_check (db::edge_relation_type rel, db::Coord d, const RegionCheckOptions &options) const
{
  std::unique_ptr<FlatEdgePairs> result (new FlatEdgePairs ());

  EdgeRelationFilter check (rel, d, options);

  for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {

    edge2edge_check_negative_or_positive<db::Shapes> edge_check (check, result->raw_edge_pairs (), options.negative, false /*=same polygons*/, false /*=same layers*/, options.shielded, true /*symmetric edge pairs*/, pc_remove (options.prop_constraint) ? 0 : p.prop_id ());
    poly2poly_check<db::Polygon> poly_check (edge_check);

    do {
      poly_check.single (*p, 0);
    } while (edge_check.prepare_next_pass ());

  }

  return result.release ();
}

RegionDelegate *
AsIfFlatRegion::merged (bool min_coherence, unsigned int min_wc, bool join_properties_on_merge) const
{
  if (empty ()) {

    return new EmptyRegion ();

  } else if (is_box ()) {

    //  take box only if min_wc == 0, otherwise clear
    if (min_wc > 0) {
      return new EmptyRegion ();
    } else {
      return clone ();
    }

  } else {

    std::unique_ptr<FlatRegion> new_region (new FlatRegion (true));
    merge_polygons_to (new_region->raw_polygons (), min_coherence, min_wc, join_properties_on_merge);

    return new_region.release ();

  }
}

RegionDelegate *
AsIfFlatRegion::sized (coord_type d, unsigned int mode) const
{
  return sized (d, d, mode);
}

RegionDelegate *
AsIfFlatRegion::sized (coord_type dx, coord_type dy, unsigned int mode) const
{
  if (empty ()) {

    //  ignore empty
    return new EmptyRegion ();

  } else if (is_box () && mode >= 2) {

    //  simplified handling for a box
    db::Box b = bbox ().enlarged (db::Vector (dx, dy));
    return region_from_box (b, db::RegionIterator (begin ()).prop_id ());

  } else if (! merged_semantics () || is_merged ()) {

    //  Generic case
    std::unique_ptr<FlatRegion> new_region (new FlatRegion ());

    db::ShapeGenerator pc (new_region->raw_polygons (), false);
    db::PolygonGenerator pg (pc, false, true);
    db::SizingPolygonFilter sf (pg, dx, dy, mode);
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      pc.set_prop_id (p.prop_id ());
      sf.put (*p);
    }

    //  in case of negative sizing the output polygons will still be merged (on positive sizing they might
    //  overlap after size and are not necessarily merged)
    if (dx < 0 && dy < 0 && is_merged ()) {
      new_region->set_is_merged (true);
    }

    return new_region.release ();

  } else {

    std::unique_ptr<FlatRegion> new_region (new FlatRegion ());

//  old implementation without property support
#if 0
    //  Generic case - the size operation will merge first
    db::EdgeProcessor ep (report_progress (), progress_desc ());
    ep.set_base_verbosity (base_verbosity ());

    //  count edges and reserve memory
    size_t n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    //  insert the polygons into the processor
    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p, ++n) {
      ep.insert (*p, n);
    }

    db::ShapeGenerator pc (new_region->raw_polygons (), true /*clear*/);
    db::PolygonGenerator pg2 (pc, false /*don't resolve holes*/, true /*min. coherence*/);
    db::SizingPolygonFilter siz (pg2, dx, dy, mode);
    db::PolygonGenerator pg (siz, false /*don't resolve holes*/, min_coherence () /*min. coherence*/);
    db::BooleanOp op (db::BooleanOp::Or);
    ep.process (pg, op);
#else

    //  Generic case
    db::ShapeGenerator pc (new_region->raw_polygons (), false);
    db::PolygonGenerator pg (pc, false, true);
    db::SizingPolygonFilter sf (pg, dx, dy, mode);
    for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
      pc.set_prop_id (p.prop_id ());
      sf.put (*p);
    }

#endif

    //  in case of negative sizing the output polygons will still be merged (on positive sizing they might
    //  overlap after size and are not necessarily merged)
    if (dx < 0 && dy < 0 && merged_semantics ()) {
      new_region->set_is_merged (true);
    }

    return new_region.release ();

  }
}

RegionDelegate *
AsIfFlatRegion::sized_inside (const Region &inside, bool outside, coord_type d, int steps, unsigned int mode) const
{
  return sized_inside (inside, outside, d, d, steps, mode);
}

RegionDelegate *
AsIfFlatRegion::sized_inside (const Region &inside, bool outside, coord_type dx, coord_type dy, int steps, unsigned int mode) const
{
  //  empirical value
  const int max_steps = 25;

  if (steps <= 0 || empty ()) {
    //  Nothing to do - NOTE: don't return EmptyRegion because we want to
    //  maintain "deepness"
    return clone ();
  }

  if (dx < 0 || dy < 0) {
    throw tl::Exception (tl::to_string (tr ("'sized_inside' operation does not make sense with negative sizing")));
  }

  if (dx == 0 && dy == 0) {
    steps = 1;
  }

  //  NOTE: it does not provide benefits to merge the outside region, so just don't
  auto inside_polygons = outside ? inside.begin () : inside.begin_merged ();
  bool inside_polygons_is_merged = outside ? inside.is_merged () : true;

  auto polygons = begin_merged ();

  std::unique_ptr<RegionDelegate> res (new FlatRegion ());
  std::unique_ptr<RegionDelegate> prev;

  int steps_from = 0;

  while (steps > 0) {

    db::Coord dx_chunk = dx, dy_chunk = dy;
    int steps_chunk = steps;

    //  We perform at most max_steps in one chunk.
    //  This is supposed to limit the search range and merge shapes instead of creating
    //  heavily overlapping ones.
    if (steps > max_steps) {
      steps_chunk = max_steps;
      dx_chunk = db::coord_traits<db::Coord>::rounded (dx * max_steps / double (steps));
      dy_chunk = db::coord_traits<db::Coord>::rounded (dy * max_steps / double (steps));
    }

    steps -= steps_chunk;
    dx -= dx_chunk;
    dy -= dy_chunk;

    //  NOTE: as we merge the inside region in the inside case, we can use distance 0
    db::Coord dist = outside ? std::max (dx_chunk, dy_chunk) : 0;
    db::sized_inside_local_operation<db::Polygon, db::Polygon, db::Polygon> op (dx_chunk, dy_chunk, steps_chunk, mode, dist, outside, inside_polygons_is_merged);

    db::local_processor<db::Polygon, db::Polygon, db::Polygon> proc;
    proc.set_base_verbosity (base_verbosity ());
    proc.set_description (progress_desc ());
    proc.set_report_progress (report_progress ());

    //  indicate chunk in the progress description
    proc.set_description (proc.description (&op) + tl::sprintf (tl::to_string (tr (" (steps %d..%d)")), steps_from + 1, steps_from + steps_chunk + 1));
    steps_from += steps_chunk;

    std::vector<db::generic_shape_iterator<db::Polygon> > others;
    others.push_back (inside_polygons);

    std::vector<db::Shapes *> results;
    db::FlatRegion *res_flat = dynamic_cast<db::FlatRegion *> (res.get ());
    tl_assert (res_flat != 0);
    results.push_back (&res_flat->raw_polygons ());
    proc.run_flat (prev.get () ? prev->begin () : polygons, others, std::vector<bool> (), &op, results);

    //  NOTE: in the last step we apply a polygon breaker in addition to "merge" so the
    //  result is granular for better deep mode performance
    if (steps > 0) {
      prev.reset (res->merged ());
      res.reset (new db::FlatRegion ());
    } else {
      res.reset (res->processed (db::PolygonBreaker (proc.max_vertex_count (), proc.area_ratio ())));
    }

  }

  return res.release ();
}

RegionDelegate *
AsIfFlatRegion::and_with (const Region &other, PropertyConstraint property_constraint) const
{
  if (empty () || other.empty ()) {

    //  Nothing to do
    return new EmptyRegion ();

  } else if (is_box () && other.is_box ()) {

    if (pc_skip (property_constraint) || pc_match (property_constraint, db::RegionIterator (begin ()).prop_id (), other.begin ().prop_id ())) {

      //  Simplified handling for boxes
      db::Box b = bbox ();
      b &= other.bbox ();

      db::properties_id_type prop_id_out = pc_norm (property_constraint, db::RegionIterator (begin ()).prop_id ());

      return region_from_box (b, prop_id_out);

    } else {
      return new EmptyRegion ();
    }

  } else if (is_box () && ! other.strict_handling ()) {

    db::properties_id_type self_prop_id = pc_skip (property_constraint) ? 0 : db::RegionIterator (begin ()).prop_id ();

    //  map AND with box to clip ..
    db::Box b = bbox ();
    std::unique_ptr<FlatRegion> new_region (new FlatRegion (false));

    db::properties_id_type prop_id_out = pc_norm (property_constraint, self_prop_id);

    std::vector<db::Polygon> clipped;
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p) {

      db::properties_id_type prop_id = p.prop_id ();
      if (pc_match (property_constraint, self_prop_id, prop_id)) {

        clipped.clear ();
        clip_poly (*p, b, clipped);

        if (prop_id_out == 0) {
          new_region->raw_polygons ().insert (clipped.begin (), clipped.end ());
        } else {
          for (auto i = clipped.begin (); i != clipped.end (); ++i) {
            new_region->raw_polygons ().insert (db::PolygonWithProperties (*i, prop_id_out));
          }
        }

      }

    }

    return new_region.release ();

  } else if (other.is_box () && ! strict_handling ()) {

    db::properties_id_type other_prop_id = pc_skip (property_constraint) ? 0 : other.begin ().prop_id ();

    //  map AND with box to clip ..
    db::Box b = other.bbox ();
    std::unique_ptr<FlatRegion> new_region (new FlatRegion (false));

    std::vector<db::Polygon> clipped;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {

      db::properties_id_type prop_id = p.prop_id ();
      if (pc_match (property_constraint, prop_id, other_prop_id)) {

        clipped.clear ();
        clip_poly (*p, b, clipped);

        db::properties_id_type prop_id_out = pc_norm (property_constraint, prop_id);
        if (prop_id_out == 0) {
          new_region->raw_polygons ().insert (clipped.begin (), clipped.end ());
        } else {
          for (auto i = clipped.begin (); i != clipped.end (); ++i) {
            new_region->raw_polygons ().insert (db::PolygonWithProperties (*i, prop_id_out));
          }
        }

      }

    }

    return new_region.release ();

  } else if (! bbox ().overlaps (other.bbox ())) {

    //  Result will be nothing
    return new EmptyRegion ();

  } else {
    return and_or_not_with (true, other, property_constraint);
  }
}

RegionDelegate *
AsIfFlatRegion::not_with (const Region &other, PropertyConstraint property_constraint) const
{
  if (empty ()) {

    //  Nothing to do
    return new EmptyRegion ();

  } else if (other.empty () && ! strict_handling ()) {

    //  Nothing to do
    return clone ()->remove_properties (pc_remove (property_constraint));

  } else if (! bbox ().overlaps (other.bbox ()) && ! strict_handling ()) {

    //  Nothing to do
    return clone ()->remove_properties (pc_remove (property_constraint));

  } else {
    return and_or_not_with (false, other, property_constraint);
  }
}

RegionDelegate *
AsIfFlatRegion::and_or_not_with (bool is_and, const Region &other, PropertyConstraint property_constraint) const
{
  if (pc_skip (property_constraint)) {

    //  Generic case
    db::EdgeProcessor ep (report_progress (), progress_desc ());
    ep.set_base_verbosity (base_verbosity ());

    //  count edges and reserve memory
    size_t n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    //  insert the polygons into the processor
    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }
    n = 1;
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }

    std::unique_ptr<FlatRegion> new_region (new FlatRegion (true));
    db::BooleanOp op (is_and ? db::BooleanOp::And : db::BooleanOp::ANotB);
    db::ShapeGenerator pc (new_region->raw_polygons (), true /*clear*/);
    db::PolygonGenerator pg (pc, false /*don't resolve holes*/, min_coherence ());
    ep.process (pg, op);

    return new_region.release ();

  } else {

    db::generic_shape_iterator<db::PolygonWithProperties> polygons (db::make_wp_iter (begin ()));

    std::unique_ptr<FlatRegion> output (new FlatRegion ());
    std::vector<db::Shapes *> results;
    results.push_back (&output->raw_polygons ());

    db::bool_and_or_not_local_operation_with_properties<db::Polygon, db::Polygon, db::Polygon> op (is_and, property_constraint);

    db::local_processor<db::PolygonWithProperties, db::PolygonWithProperties, db::PolygonWithProperties> proc;
    proc.set_base_verbosity (base_verbosity ());
    proc.set_description (progress_desc ());
    proc.set_report_progress (report_progress ());

    std::vector<db::generic_shape_iterator<db::PolygonWithProperties> > others;
    others.push_back (db::make_wp_iter (other.begin ()));

    proc.run_flat (polygons, others, std::vector<bool> (), &op, results);

    return output.release ();

  }
}

std::pair<RegionDelegate *, RegionDelegate *>
AsIfFlatRegion::andnot_with (const Region &other, PropertyConstraint property_constraint) const
{
  if (empty ()) {

    //  Nothing to do
    return std::make_pair (new EmptyRegion (), new EmptyRegion ());

  } else if (other.empty () && ! strict_handling ()) {

    //  Nothing to do
    return std::make_pair (new EmptyRegion (), clone ()->remove_properties (pc_remove (property_constraint)));

  } else if (! bbox ().overlaps (other.bbox ()) && ! strict_handling ()) {

    //  Nothing to do
    return std::make_pair (new EmptyRegion (), clone ()->remove_properties (pc_remove (property_constraint)));

  } else if (pc_skip (property_constraint)) {

    //  Generic case
    db::EdgeProcessor ep (report_progress (), progress_desc ());
    ep.set_base_verbosity (base_verbosity ());

    //  count edges and reserve memory
    size_t n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    //  insert the polygons into the processor
    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }
    n = 1;
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }

    std::unique_ptr<FlatRegion> new_region1 (new FlatRegion (true));
    db::BooleanOp op1 (db::BooleanOp::And);
    db::ShapeGenerator pc1 (new_region1->raw_polygons (), true /*clear*/);
    db::PolygonGenerator pg1 (pc1, false /*don't resolve holes*/, min_coherence ());

    std::unique_ptr<FlatRegion> new_region2 (new FlatRegion (true));
    db::BooleanOp op2 (db::BooleanOp::ANotB);
    db::ShapeGenerator pc2 (new_region2->raw_polygons (), true /*clear*/);
    db::PolygonGenerator pg2 (pc2, false /*don't resolve holes*/, min_coherence ());

    std::vector<std::pair<db::EdgeSink *, db::EdgeEvaluatorBase *> > procs;
    procs.push_back (std::make_pair (&pg1, &op1));
    procs.push_back (std::make_pair (&pg2, &op2));
    ep.process (procs);

    return std::make_pair (new_region1.release (), new_region2.release ());

  } else {

    db::generic_shape_iterator<db::PolygonWithProperties> polygons (db::make_wp_iter (begin ()));

    std::unique_ptr<FlatRegion> output1 (new FlatRegion ());
    std::unique_ptr<FlatRegion> output2 (new FlatRegion ());
    std::vector<db::Shapes *> results;
    results.push_back (&output1->raw_polygons ());
    results.push_back (&output2->raw_polygons ());

    db::two_bool_and_not_local_operation_with_properties<db::Polygon, db::Polygon, db::Polygon> op (property_constraint);

    db::local_processor<db::PolygonWithProperties, db::PolygonWithProperties, db::PolygonWithProperties> proc;
    proc.set_base_verbosity (base_verbosity ());
    proc.set_description (progress_desc ());
    proc.set_report_progress (report_progress ());

    std::vector<db::generic_shape_iterator<db::PolygonWithProperties> > others;
    others.push_back (db::make_wp_iter (other.begin ()));

    proc.run_flat (polygons, others, std::vector<bool> (), &op, results);

    return std::make_pair (output1.release (), output2.release ());

  }
}

RegionDelegate *
AsIfFlatRegion::xor_with (const Region &other, PropertyConstraint prop_constraint) const
{
  if (empty () && ! other.strict_handling ()) {

    return other.delegate ()->clone ();

  } else if (other.empty () && ! strict_handling ()) {

    return clone ();

  } else if (! bbox ().overlaps (other.bbox ()) && ! strict_handling () && ! other.strict_handling ()) {

    //  Simplified handling for disjunct case
    return or_with (other, prop_constraint);

  } else {

    //  TODO: implement property constraint

    //  Generic case
    db::EdgeProcessor ep (report_progress (), progress_desc ());
    ep.set_base_verbosity (base_verbosity ());

    //  count edges and reserve memory
    size_t n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    //  insert the polygons into the processor
    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }
    n = 1;
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }

    std::unique_ptr<FlatRegion> new_region (new FlatRegion (true));
    db::BooleanOp op (db::BooleanOp::Xor);
    db::ShapeGenerator pc (new_region->raw_polygons (), true /*clear*/);
    db::PolygonGenerator pg (pc, false /*don't resolve holes*/, min_coherence ());
    ep.process (pg, op);

    return new_region.release ();

  }
}

RegionDelegate *
AsIfFlatRegion::or_with (const Region &other, PropertyConstraint /*prop_constraint*/) const
{
  if (empty () && ! other.strict_handling ()) {

    return other.delegate ()->clone ();

  } else if (other.empty () && ! strict_handling ()) {

    //  Nothing to do
    return clone ();

  } else if (! bbox ().overlaps (other.bbox ()) && ! strict_handling () && ! other.strict_handling ()) {

    //  Simplified handling for disjunct case
    return add (other);

  } else {

    //  TODO: implement property constraint

    //  Generic case
    db::EdgeProcessor ep (report_progress (), progress_desc ());
    ep.set_base_verbosity (base_verbosity ());

    //  count edges and reserve memory
    size_t n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p) {
      n += p->vertices ();
    }
    ep.reserve (n);

    //  insert the polygons into the processor
    n = 0;
    for (RegionIterator p (begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }
    n = 1;
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p, n += 2) {
      ep.insert (*p, n);
    }

    std::unique_ptr<FlatRegion> new_region (new FlatRegion (true));
    db::BooleanOp op (db::BooleanOp::Or);
    db::ShapeGenerator pc (new_region->raw_polygons (), true /*clear*/);
    db::PolygonGenerator pg (pc, false /*don't resolve holes*/, min_coherence ());
    ep.process (pg, op);

    return new_region.release ();

  }
}

RegionDelegate *
AsIfFlatRegion::add (const Region &other) const
{
  const FlatRegion *other_flat = dynamic_cast<const FlatRegion *> (other.delegate ());
  if (other_flat) {

    std::unique_ptr<FlatRegion> new_region (new FlatRegion (*other_flat));
    new_region->set_is_merged (false);
    new_region->invalidate_cache ();

    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      if (p.prop_id () == 0) {
        new_region->raw_polygons ().insert (*p);
      } else {
        new_region->raw_polygons ().insert (db::PolygonWithProperties (*p, p.prop_id ()));
      }
    }

    return new_region.release ();

  } else {

    std::unique_ptr<FlatRegion> new_region (new FlatRegion (false /*not merged*/));

    for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
      if (p.prop_id () == 0) {
        new_region->raw_polygons ().insert (*p);
      } else {
        new_region->raw_polygons ().insert (db::PolygonWithProperties (*p, p.prop_id ()));
      }
    }
    for (RegionIterator p (other.begin ()); ! p.at_end (); ++p) {
      if (p.prop_id () == 0) {
        new_region->raw_polygons ().insert (*p);
      } else {
        new_region->raw_polygons ().insert (db::PolygonWithProperties (*p, p.prop_id ()));
      }
    }

    return new_region.release ();

  }
}

static void
deliver_shapes_of_nets_recursive (db::Shapes &out, const db::Circuit *circuit, const LayoutToNetlist *l2n, unsigned int lid, NetPropertyMode prop_mode, const tl::Variant &net_prop_name, const db::ICplxTrans &tr, const std::set<const db::Net *> *net_filter)
{
  db::CplxTrans dbu_trans (l2n->internal_layout ()->dbu ());
  auto dbu_trans_inv = dbu_trans.inverted ();

  for (auto n = circuit->begin_nets (); n != circuit->end_nets (); ++n) {

    if (! net_filter || net_filter->find (n.operator-> ()) != net_filter->end ()) {
      db::properties_id_type prop_id = db::NetBuilder::make_netname_propid (prop_mode, net_prop_name, *n);
      l2n->shapes_of_net (*n, lid, true, out, prop_id, tr);
    }

    //  dive into subcircuits
    for (auto sc = circuit->begin_subcircuits (); sc != circuit->end_subcircuits (); ++sc) {
      const db::Circuit *circuit_ref = sc->circuit_ref ();
      db::ICplxTrans tr_ref = tr * (dbu_trans_inv * sc->trans () * dbu_trans);
      deliver_shapes_of_nets_recursive (out, circuit_ref, l2n, lid, prop_mode, net_prop_name, tr_ref, net_filter);
    }

  }
}

RegionDelegate *
AsIfFlatRegion::nets (LayoutToNetlist *l2n, NetPropertyMode prop_mode, const tl::Variant &net_prop_name, const std::vector<const db::Net *> *net_filter) const
{
  if (! l2n->is_netlist_extracted ()) {
    throw tl::Exception (tl::to_string (tr ("The netlist has not been extracted yet")));
  }

  std::unique_ptr<db::FlatRegion> result (new db::FlatRegion ());
  tl::optional<unsigned int> li = l2n->layer_by_original (this);

  if (! li.has_value ()) {
    throw tl::Exception (tl::to_string (tr ("The given layer is not an original layer used in netlist extraction")));
  }

  if (l2n->netlist ()->top_circuit_count () == 0) {
    throw tl::Exception (tl::to_string (tr ("No top circuit found in netlist")));
  } else if (l2n->netlist ()->top_circuit_count () > 1) {
    throw tl::Exception (tl::to_string (tr ("More than one top circuit found in netlist")));
  }
  const db::Circuit *top_circuit = l2n->netlist ()->begin_top_down ().operator-> ();

  std::set<const db::Net *> net_filter_set;
  if (net_filter) {
    net_filter_set.insert (net_filter->begin (), net_filter->end ());
  }

  deliver_shapes_of_nets_recursive (result->raw_polygons (), top_circuit, l2n, li.value (), prop_mode, net_prop_name, db::ICplxTrans (), net_filter ? &net_filter_set : 0);

  return result.release ();
}

void
AsIfFlatRegion::insert_into (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer) const
{
  //  improves performance when inserting an original layout into the same layout
  db::LayoutLocker locker (layout);

  db::Shapes &shapes = layout->cell (into_cell).shapes (into_layer);
  for (RegionIterator p (begin ()); ! p.at_end (); ++p) {
    db::properties_id_type prop_id = p.prop_id ();
    if (prop_id != 0) {
      shapes.insert (db::PolygonWithProperties (*p, prop_id));
    } else {
      shapes.insert (*p);
    }
  }
}

bool
AsIfFlatRegion::equals (const Region &other) const
{
  if (empty () != other.empty ()) {
    return false;
  }
  if (count () != other.count ()) {
    return false;
  }
  RegionIterator o1 (begin ());
  RegionIterator o2 (other.begin ());
  while (! o1.at_end () && ! o2.at_end ()) {
    if (*o1 != *o2) {
      return false;
    }
    ++o1;
    ++o2;
  }
  return true;
}

bool
AsIfFlatRegion::less (const Region &other) const
{
  if (empty () != other.empty ()) {
    return empty () < other.empty ();
  }
  if (count () != other.count ()) {
    return (count () < other.count ());
  }
  RegionIterator o1 (begin ());
  RegionIterator o2 (other.begin ());
  while (! o1.at_end () && ! o2.at_end ()) {
    if (*o1 != *o2) {
      return *o1 < *o2;
    }
    ++o1;
    ++o2;
  }
  return false;
}

}

