

#include "tcurves.h"
// #include "tpalette.h"
#include "tvectorimage.h"
#include "tvectorimageP.h"
#include "tstroke.h"
// #include "tgl.h"
#include "tvectorrenderdata.h"
#include "tmathutil.h"
// #include "tdebugmessage.h"
#include "tofflinegl.h"
// #include "tcolorstyles.h"
#include "tpaletteutil.h"
#include "tthreadmessage.h"
#include "tsimplecolorstyles.h"
#include "tcomputeregions.h"

#include <memory>
#include <QSet>

//=============================================================================
typedef TVectorImage::IntersectionBranch IntersectionBranch;

namespace {

typedef std::set<int> DisabledStrokeStyles;

// uso getDisabledStrokeStyleSet() invece che accedere direttamente alla
// variabile per assicurarmi che il tutto funzioni anche quando viene
// usato PRIMA del main (per iniziativa di un costruttore di una variabile
// globale, p.es.).
// per l'idioma: cfr. Modern C++ design, Andrei Alexandrescu, Addison Wesley
// 2001, p.133

inline DisabledStrokeStyles &getDisabledStrokeStyleSet() {
  static DisabledStrokeStyles disabledStokeStyles;
  return disabledStokeStyles;
}

inline bool isStrokeStyleEnabled__(int index) {
  DisabledStrokeStyles &disabledSet = getDisabledStrokeStyleSet();
  return disabledSet.find(index) == disabledSet.end();
}

}  // namespace

//=============================================================================

/*!
Permette di copiare effettuare delle copie delle curve
*/
/*
template <class Container>
class StrokeArrayInsertIterator
{
  Container& container;

public:
  explicit    StrokeArrayInsertIterator(Container& Line)
    :container(Line)
  {};

  StrokeArrayInsertIterator& operator=(const VIStroke* value )
  {
    TStroke *stroke = new TStroke(*(value->m_s));

    stroke->setId(value->m_s->getId());
    container.push_back(new VIStroke(stroke));
    return *this;
  };

  StrokeArrayInsertIterator&  operator*()       { return *this; }
  StrokeArrayInsertIterator&  operator++()       { return *this; }
  StrokeArrayInsertIterator   operator++(int val){ return *this; }
};
*/

//=============================================================================
/*!
TVectorImage::Imp: implementation of TVectorImage class
\relates  TVectorImage
*/

//=============================================================================

TVectorImage::Imp::Imp(TVectorImage *vi)
    : m_areValidRegions(false)
    , m_notIntersectingStrokes(false)
    , m_computeRegions(true)
    , m_autocloseTolerance(c_newAutocloseTolerance)
    , m_maxGroupId(1)
    , m_maxGhostGroupId(1)
    , m_mutex(new TThread::Mutex())
    , m_vi(vi)
    , m_intersectionData(0)
    , m_computedAlmostOnce(false)
    , m_justLoaded(false)
    , m_insideGroup(TGroupId())
    , m_minimizeEdges(true)
#ifdef NEW_REGION_FILL
    , m_regionFinder(0)
#endif
{
#ifdef NEW_REGION_FILL
  resetRegionFinder();
#endif
  initRegionsData();
}

TVectorImage::Imp::~Imp() {
  // delete m_regionFinder;
  deleteRegionsData();
  delete m_mutex;
}

//=============================================================================

TVectorImage::TVectorImage(bool loaded) : m_imp(new TVectorImage::Imp(this)) {
  if (loaded) m_imp->m_justLoaded = true;
}

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

TVectorImage::~TVectorImage() {}

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

int TVectorImage::isInsideGroup() const {
  return m_imp->m_insideGroup.getDepth();
}

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

int TVectorImage::addStrokeToGroup(TStroke *stroke, int strokeIndex) {
  if (!m_imp->m_strokes[strokeIndex]->m_groupId.isGrouped())
    return addStroke(stroke, true);
  for (int i = m_imp->m_strokes.size() - 1; i >= 0; i--)
    if (m_imp->m_strokes[i]->m_groupId ==
        m_imp->m_strokes[strokeIndex]->m_groupId) {
      m_imp->insertStrokeAt(
          new VIStroke(stroke, m_imp->m_strokes[i]->m_groupId), i + 1);
      return i + 1;
    }
  assert(false);
  return -1;
}

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

int TVectorImage::addStroke(TStroke *stroke, bool discardPoints, bool underAll) {
  if (discardPoints) {
    TRectD bBox = stroke->getBBox();
    if (bBox.x0 == bBox.x1 && bBox.y0 == bBox.y1)  // empty stroke: discard
      return -1;
  }

  if (m_imp->m_insideGroup != TGroupId()) {
    int i;
    for (i = m_imp->m_strokes.size() - 1; i >= 0; i--)
      if (m_imp->m_insideGroup.isParentOf(m_imp->m_strokes[i]->m_groupId)) {
        if (underAll) {
          int j = 0;
          for (j = i - 1; j >= 0 && m_imp->m_insideGroup.isParentOf(
                                        m_imp->m_strokes[j]->m_groupId);
               j--);
          m_imp->insertStrokeAt(
              new VIStroke(stroke, m_imp->m_strokes[j + 1]->m_groupId), j + 1);
          return j + 1;
        } else {
          m_imp->insertStrokeAt(
              new VIStroke(stroke, m_imp->m_strokes[i]->m_groupId), i + 1);
          return i + 1;
        }
      }
  }

  TGroupId gid;
  if (m_imp->m_strokes.empty() ||
      m_imp->m_strokes.back()->m_groupId.isGrouped() != 0)
    gid = TGroupId(this, true);
  else
    gid = m_imp->m_strokes.back()->m_groupId;
  
  if (underAll) {
    m_imp->insertStrokeAt(new VIStroke(stroke, gid), 0);
    m_imp->m_areValidRegions = false;
    return 0;
  } else {
    m_imp->m_areValidRegions = false;
    m_imp->m_strokes.push_back(new VIStroke(stroke, gid));
    return m_imp->m_strokes.size() - 1;
  }
}

int TVectorImage::addStrokeBelow(TStroke *stroke, const QSet<int> &aboveStyles,
                                 bool discardPoints) {
  if (aboveStyles.isEmpty())
    return addStroke(stroke, discardPoints);
  if (discardPoints) {
    TRectD bBox = stroke->getBBox();
    if (bBox.x0 == bBox.x1 && bBox.y0 == bBox.y1)  // empty stroke: discard
      return -1;
  }

  if (m_imp->m_insideGroup != TGroupId()) {
    int i;
    for (i = m_imp->m_strokes.size() - 1; i >= 0; i--)
      if (m_imp->m_insideGroup.isParentOf(m_imp->m_strokes[i]->m_groupId)) {
        int j = 0;
        for (j = i - 1;
             j >= 0 &&
             m_imp->m_insideGroup.isParentOf(m_imp->m_strokes[j]->m_groupId) &&
             aboveStyles.contains(m_imp->m_strokes[j]->m_s->getStyle());
             j--);
        m_imp->insertStrokeAt(
            new VIStroke(stroke, m_imp->m_strokes[j + 1]->m_groupId), j + 1);
        return j + 1;
      }
  }

  TGroupId gid;
  if (m_imp->m_strokes.empty() ||
      m_imp->m_strokes.back()->m_groupId.isGrouped() != 0)
    gid = TGroupId(this, true);
  else
      gid = m_imp->m_strokes.back()->m_groupId;

  int i;
  for (i = m_imp->m_strokes.size() - 1;
       i >= 0 && !isStrokeGrouped(i) &&
       aboveStyles.contains(m_imp->m_strokes[i]->m_s->getStyle());
       i--);
  m_imp->insertStrokeAt(new VIStroke(stroke, gid), i + 1);
  m_imp->m_areValidRegions = false;
  return i + 1;
}

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

void TVectorImage::moveStrokes(int fromIndex, int count, int moveBefore) {
#ifdef _DEBUG
  m_imp->checkGroups();
#endif
  m_imp->moveStrokes(fromIndex, count, moveBefore, true);
#ifdef _DEBUG
  m_imp->checkGroups();
#endif
}

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

void TVectorImage::Imp::moveStrokes(int fromIndex, int count, int moveBefore,
                                    bool regroup) {
  assert(fromIndex >= 0 && fromIndex < (int)m_strokes.size());
  assert(moveBefore >= 0 && moveBefore <= (int)m_strokes.size());
  assert(count > 0);

  assert(fromIndex != moveBefore);

  for (int i = 0; i < count; i++)
    if (fromIndex < moveBefore)
      moveStroke(fromIndex, moveBefore);
    else
      moveStroke(fromIndex + i, moveBefore + i);

  std::vector<int> changedStrokes;
  if (regroup) regroupGhosts(changedStrokes);
  if (!changedStrokes.empty())
    notifyChangedStrokes(changedStrokes, std::vector<TStroke *>(), false);
}

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

void TVectorImage::insertStrokeAt(VIStroke *vs, int strokeIndex,
                                  bool recomputeRegions) {
  m_imp->insertStrokeAt(vs, strokeIndex, recomputeRegions);
}

/*
void TVectorImage::insertStrokeAt(TStroke *stroke, int strokeIndex, const
TGroupId& id)
{
VIStroke* vs;

vs = new VIStroke(stroke, id);

m_imp->insertStrokeAt(vs, strokeIndex);

}
*/

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

/*
TRectD TVectorImage::addStroke(const std::vector<TThickPoint> &points)
{
//  era:  TStroke *stroke = makeTStroke(points);
  TStroke *stroke = TStroke::interpolate(points, 5.0);

  m_imp->m_strokes.push_back(new VIStroke( stroke) );
  m_imp->m_areValidRegions = false;

  return stroke->getBBox();
}
*/

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

static bool isRegionWithStroke(TRegion *region, TStroke *s) {
  for (UINT i = 0; i < region->getEdgeCount(); i++)
    if (region->getEdge(i)->m_s == s) return true;
  return false;
}

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

static void deleteSubRegionWithStroke(TRegion *region, TStroke *s) {
  for (int i = 0; i < (int)region->getSubregionCount(); i++) {
    deleteSubRegionWithStroke(region->getSubregion(i), s);
    if (isRegionWithStroke(region->getSubregion(i), s)) {
      TRegion *r = region->getSubregion(i);
      r->moveSubregionsTo(region);
      assert(r->getSubregionCount() == 0);
      region->deleteSubregion(i);
      delete r;
      i--;
    }
  }
}

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

TStroke *TVectorImage::removeStroke(int index, bool doComputeRegions) {
  return m_imp->removeStroke(index, doComputeRegions);
}

TStroke *TVectorImage::Imp::removeStroke(int index, bool doComputeRegions) {
  assert(index >= 0 && index < (int)m_strokes.size());
  QMutexLocker sl(m_mutex);

  VIStroke *stroke = m_strokes[index];

  eraseIntersection(index);

  m_strokes.erase(m_strokes.begin() + index);

  if (m_computedAlmostOnce) {
    reindexEdges(index);
    if (doComputeRegions) computeRegions();
  }

  return stroke->m_s;
}

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

void TVectorImage::removeStrokes(const std::vector<int> &toBeRemoved,
                                 bool deleteThem, bool recomputeRegions) {
  m_imp->removeStrokes(toBeRemoved, deleteThem, recomputeRegions);
}

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

void TVectorImage::Imp::removeStrokes(const std::vector<int> &toBeRemoved,
                                      bool deleteThem, bool recomputeRegions) {
  QMutexLocker sl(m_mutex);

  for (int i = toBeRemoved.size() - 1; i >= 0; i--) {
    assert(i == 0 || toBeRemoved[i - 1] < toBeRemoved[i]);
    UINT index = toBeRemoved[i];

    eraseIntersection(index);
    if (deleteThem) delete m_strokes[index];
    m_strokes.erase(m_strokes.begin() + index);
  }

  if (m_computedAlmostOnce && !toBeRemoved.empty()) {
    reindexEdges(toBeRemoved, false);

    if (recomputeRegions)
      computeRegions();
    else
      m_areValidRegions = false;
  }
}

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

void TVectorImage::deleteStroke(int index) {
  TStroke *stroke = removeStroke(index);
  delete stroke;
}

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

void TVectorImage::deleteStroke(VIStroke *stroke) {
  UINT index = 0;
  for (; index < m_imp->m_strokes.size(); index++)
    if (m_imp->m_strokes[index] == stroke) {
      deleteStroke(index);
      return;
    }
}

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

/*
void TVectorImage::validateRegionEdges(TStroke* stroke, bool invalidate)
{
if (invalidate)
  for (UINT i=0; i<getRegionCount(); i++)
    {
    TRegion *r = getRegion(i);
//  if ((*cit)->getBBox().contains(stroke->getBBox()))
    for (UINT j=0; j<r->getEdgeCount(); j++)
      {
      TEdge* edge = r->getEdge(j);
      if (edge->m_s == stroke)
        edge->m_w0 = edge->m_w1 = -1;
      }
    }
else
 for (UINT i=0; i<getRegionCount(); i++)
    {
    TRegion *r = getRegion(i);
//  if ((*cit)->getBBox().contains(stroke->getBBox()))
    for (UINT j=0; j<r->getEdgeCount(); j++)
      {
      TEdge* edge = r->getEdge(j);
      if (edge->m_w0==-1)
        {
        int index;
        double t, dummy;
        edge->m_s->getNearestChunk(edge->m_p0, t, index, dummy);
        edge->m_w0 = getWfromChunkAndT(edge->m_s, index, t);
        edge->m_s->getNearestChunk(edge->m_p1, t, index, dummy);
        edge->m_w1 = getWfromChunkAndT(edge->m_s, index, t);
        }
      }
    }


}
*/
//-----------------------------------------------------------------------------

UINT TVectorImage::getStrokeCount() const { return m_imp->m_strokes.size(); }

//-----------------------------------------------------------------------------
/*
void  TVectorImage::addSeed(const TPointD& p, const TPixel& color)
{
m_imp->m_seeds.push_back(TFillSeed(color, p, NULL));
}
*/
//-----------------------------------------------------------------------------

UINT TVectorImage::getRegionCount() const {
  //  assert( m_imp->m_areValidRegions || m_imp->m_regions.empty());
  return m_imp->m_regions.size();
}

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

TRegion *TVectorImage::getRegion(UINT index) const {
  assert(index < m_imp->m_regions.size());
  //  assert( m_imp->m_areValidRegions );
  return m_imp->m_regions[index];
}

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

TRegion *TVectorImage::getRegion(TRegionId regId) const {
  int index = getStrokeIndexById(regId.m_strokeId);

  assert(m_imp->m_areValidRegions);

  TRegion *reg = m_imp->getRegion(regId, index);
  // assert( reg );
  return reg;
}

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

TRegion *TVectorImage::Imp::getRegion(TRegionId regId, int index) const {
  assert(index != -1);
  if (index == -1) return 0;

  assert(index < (int)m_strokes.size());
  if (index >= (int)m_strokes.size()) return 0;

  std::list<TEdge *> &edgeList = m_strokes[index]->m_edgeList;

  std::list<TEdge *>::iterator endList = edgeList.end();
  double w0;
  double w1;
  for (std::list<TEdge *>::iterator it = edgeList.begin(); it != endList;
       ++it) {
    w0 = (*it)->m_w0;
    w1 = (*it)->m_w1;

    if (w0 < w1) {
      if (w0 < regId.m_midW && regId.m_midW < w1 && regId.m_direction)
        return (*it)->m_r;
    } else {
      if (w1 < regId.m_midW && regId.m_midW < w0 && !regId.m_direction)
        return (*it)->m_r;
    }
  }

#ifdef _DEBUG
  TPointD cp1 = m_strokes[index]->m_s->getControlPoint(0);
  TPointD cp2 = m_strokes[index]->m_s->getControlPoint(
      m_strokes[index]->m_s->getControlPointCount() - 1);
#endif

  return 0;
}

/*
TRegion* TVectorImage::getRegion(TRegionId regId) const
{
  int index = getStrokeIndexById(regId.m_strokeId);
  assert(index!=-1);
  if( index == -1 )
    return 0;

  assert( index < (int)m_imp->m_strokes.size() );
  if( index >= (int)m_imp->m_strokes.size() )
    return 0;

  std::list<TEdge*> &edgeList = m_imp->m_strokes[index]->m_edgeList;

  std::list<TEdge*>::iterator endList = edgeList.end();
  double w0;
  double w1;
  for(list<TEdge*>::iterator it= edgeList.begin(); it!=endList; ++it)
  {
    w0 = (*it)->m_w0;
    w1 = (*it)->m_w1;

    if(w0<w1)
    {
      if( w0 < regId.m_midW && regId.m_midW < w1 && regId.m_direction )
        return (*it)->m_r;
    }
    else
    {
      if( w1 < regId.m_midW && regId.m_midW < w0 && !regId.m_direction )
        return (*it)->m_r;
    }
  }

  return 0;
}
*/
//-----------------------------------------------------------------------------

void TVectorImage::setEdgeColors(int strokeIndex, int leftColorIndex,
                                 int rightColorIndex) {
  std::list<TEdge *> &ll = m_imp->m_strokes[strokeIndex]->m_edgeList;

  std::list<TEdge *>::const_iterator l   = ll.begin();
  std::list<TEdge *>::const_iterator l_e = ll.end();
  for (; l != l_e; ++l) {
    // double w0 = (*l)->m_w0, w1 = (*l)->m_w1;
    if ((*l)->m_w0 > (*l)->m_w1) {
      if (leftColorIndex != -1)
        (*l)->m_styleId = leftColorIndex;
      else if (rightColorIndex != -1)
        (*l)->m_styleId = rightColorIndex;
    } else {
      if (rightColorIndex != -1)
        (*l)->m_styleId = rightColorIndex;
      else if (leftColorIndex != -1)
        (*l)->m_styleId = leftColorIndex;
    }
  }
}

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

TStroke *TVectorImage::getStroke(UINT index) const {
  assert(index < m_imp->m_strokes.size());
  return m_imp->m_strokes[index]->m_s;
}

VIStroke *TVectorImage::getVIStroke(UINT index) const {
  assert(index < m_imp->m_strokes.size());
  return m_imp->m_strokes[index];
}

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

VIStroke *TVectorImage::getStrokeById(int id) const {
  int n = m_imp->m_strokes.size();
  for (int i = 0; i < n; i++)
    if (m_imp->m_strokes[i]->m_s->getId() == id) return m_imp->m_strokes[i];
  return 0;
}

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

int TVectorImage::getStrokeIndexById(int id) const {
  int n = m_imp->m_strokes.size();

  for (int i = 0; i < n; i++)
    if (m_imp->m_strokes[i]->m_s->getId() == id) return i;

  return -1;
}

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

int TVectorImage::getStrokeIndex(TStroke *stroke) const {
  int n = m_imp->m_strokes.size();

  for (int i = 0; i < n; i++)
    if (m_imp->m_strokes[i]->m_s == stroke) return i;

  return -1;
}

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

TRectD TVectorImage::getBBox() const {
  UINT strokeCount = m_imp->m_strokes.size();
  if (strokeCount == 0) return TRectD();

  TPalette *plt = getPalette();
  TRectD bbox;

  for (UINT i = 0; i < strokeCount; ++i) {
    TStroke *stroke    = m_imp->m_strokes[i]->m_s;
    TColorStyle *style = 0;
    if (plt) style = plt->getStyle(m_imp->m_strokes[i]->m_s->getStyle());
    if (!style) continue;
    // reimplemented in TRasterImagePatternStrokeStyle,
    // TVectorImagePatternStrokeStyle and FlowLineStrokeStyle
    TRectD r = style->getStrokeBBox(stroke);
    bbox     = ((i == 0) ? r : bbox + r);
  }

  return bbox;
}

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

bool TVectorImage::getNearestStroke(const TPointD &p, double &outW,
                                    UINT &strokeIndex, double &dist2,
                                    bool onlyInCurrentGroup) const {
  dist2       = (std::numeric_limits<double>::max)();
  strokeIndex = getStrokeCount();
  outW        = -1;

  double tempdis2, tempPar;

  for (int i = 0; i < (int)m_imp->m_strokes.size(); ++i) {
    if (onlyInCurrentGroup && !inCurrentGroup(i)) continue;
    TStroke *s = m_imp->m_strokes[i]->m_s;
    tempPar    = s->getW(p);

    tempdis2 = tdistance2(TThickPoint(p, 0), s->getThickPoint(tempPar));

    if (tempdis2 < dist2) {
      outW        = tempPar;
      dist2       = tempdis2;
      strokeIndex = i;
    }
  }

  return dist2 < (std::numeric_limits<double>::max)();
}

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

#if defined(LINUX) || defined(FREEBSD) || defined(MACOSX)
void TVectorImage::render(const TVectorRenderData &rd, TRaster32P &ras) {
  // hardRenderVectorImage(rd,ras,this);
}
#endif

//-----------------------------------------------------------------------------
// #include "timage_io.h"

TRaster32P TVectorImage::render(bool onlyStrokes) {
  TRect bBox = convert(getBBox());
  if (bBox.isEmpty()) return (TRaster32P)0;

  std::unique_ptr<TOfflineGL> offlineGlContext(new TOfflineGL(bBox.getSize()));
  offlineGlContext->clear(TPixel32(0, 0, 0, 0));
  offlineGlContext->makeCurrent();
  TVectorRenderData rd(TTranslation(-convert(bBox.getP00())),
                       TRect(bBox.getSize()), getPalette(), 0, true, true);
  rd.m_drawRegions = !onlyStrokes;
  offlineGlContext->draw(this, rd, false);

  return offlineGlContext->getRaster()->clone();

  // hardRenderVectorImage(rd,ras,this);
}

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

TRegion *TVectorImage::getRegion(const TPointD &p) {
#ifndef NEW_REGION_FILL
  if (!isComputedRegionAlmostOnce()) return 0;

  if (!m_imp->m_areValidRegions) m_imp->computeRegions();
#endif

  return m_imp->getRegion(p);
}

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

TRegion *TVectorImage::Imp::getRegion(const TPointD &p) {
  int strokeIndex = (int)m_strokes.size() - 1;

  while (strokeIndex >= 0) {
    for (UINT regionIndex = 0; regionIndex < m_regions.size(); regionIndex++)
      if (areDifferentGroup(strokeIndex, false, regionIndex, true) == -1 &&
          m_regions[regionIndex]->contains(p))
        return m_regions[regionIndex]->getRegion(p);
    int curr = strokeIndex;
    while (strokeIndex >= 0 &&
           areDifferentGroup(curr, false, strokeIndex, false) == -1)
      strokeIndex--;
  }

  return 0;
}

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

int TVectorImage::fillStrokes(const TPointD &p, int styleId) {
  UINT index;
  double outW, dist2;

  if (getNearestStroke(p, outW, index, dist2, true)) {
    double thick = getStroke(index)->getThickPoint(outW).thick * 1.25;
    if (thick < 0.5) thick = 0.5;

    if (dist2 > thick * thick) return -1;
    assert(index >= 0 && index < m_imp->m_strokes.size());
    int ret = m_imp->m_strokes[index]->m_s->getStyle();
    m_imp->m_strokes[index]->m_s->setStyle(styleId);
    return ret;
  }

  return -1;
}

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

#ifdef NEW_REGION_FILL

void TVectorImage::resetRegionFinder() { m_imp->resetRegionFinder(); }
#else
//------------------------------------------------------------------

int TVectorImage::fill(const TPointD &p, int newStyleId, bool onlyEmpty) {
  TRegion *r = getRegion(p);
  if (onlyEmpty && r && r->getStyle() != 0) return -1;

  if (!m_imp->m_areValidRegions) m_imp->computeRegions();
  return m_imp->fill(p, newStyleId);
}
#endif

//-----------------------------------------------------------------------------
/*
void TVectorImage::autoFill(int styleId)
{
m_imp->autoFill(styleId, true);
}

void TVectorImage::Imp::autoFill(int styleId, bool oddLevel)
{
if (!m_areValidRegions)
  computeRegions();
for (UINT i = 0; i<m_regions.size(); i++)
  {
  if (oddLevel)
    m_regions[i]->setStyle(styleId);
  m_regions[i]->autoFill(styleId, !oddLevel);
  }
}
*/
//-----------------------------------------------------------------------------
/*
void TRegion::autoFill(int styleId, bool oddLevel)
{
for (UINT i = 0; i<getSubregionCount(); i++)
  {
  TRegion* r = getSubregion(i);
  if (oddLevel)
    r->setStyle(styleId);
  r->autoFill(styleId, !oddLevel);
  }

}
*/
//-----------------------------------------------------------------------------

int TVectorImage::Imp::fill(const TPointD &p, int styleId) {
  int strokeIndex = (int)m_strokes.size() - 1;

  while (strokeIndex >= 0) {
    if (!inCurrentGroup(strokeIndex)) {
      strokeIndex--;
      continue;
    }
    for (UINT regionIndex = 0; regionIndex < m_regions.size(); regionIndex++)
      if (areDifferentGroup(strokeIndex, false, regionIndex, true) == -1 &&
          m_regions[regionIndex]->contains(p))
        return m_regions[regionIndex]->fill(p, styleId);
    int curr = strokeIndex;
    while (strokeIndex >= 0 &&
           areDifferentGroup(curr, false, strokeIndex, false) == -1)
      strokeIndex--;
  }

  return -1;
}

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

bool TVectorImage::selectFill(const TRectD &selArea, TStroke *s, int newStyleId,
                              bool onlyUnfilled, bool fillAreas,
                              bool fillLines) {
  if (!m_imp->m_areValidRegions) m_imp->computeRegions();
  return m_imp->selectFill(selArea, s, newStyleId, onlyUnfilled, fillAreas,
                           fillLines);
}

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

bool TVectorImage::Imp::selectFill(const TRectD &selArea, TStroke *s,
                                   int newStyleId, bool onlyUnfilled,
                                   bool fillAreas, bool fillLines) {
  bool hitSome = false;

  if (s) {
    TVectorImage aux;
    aux.addStroke(s);
    aux.findRegions();
    for (UINT j = 0; j < aux.getRegionCount(); j++) {
      TRegion *r0 = aux.getRegion(j);
      if (fillAreas)
        for (UINT i = 0; i < m_regions.size(); i++) {
          TRegion *r1 = m_regions[i];

          if (m_insideGroup != TGroupId() &&
              !m_insideGroup.isParentOf(
                  m_strokes[r1->getEdge(0)->m_index]->m_groupId))
            continue;

          if ((!onlyUnfilled || r1->getStyle() == 0) && r0->contains(*r1)) {
            r1->setStyle(newStyleId);
            hitSome = true;
          }
        }
      if (fillLines)
        for (UINT i = 0; i < m_strokes.size(); i++) {
          if (!inCurrentGroup(i)) continue;

          TStroke *s1 = m_strokes[i]->m_s;
          if ((!onlyUnfilled || s1->getStyle() == 0) && r0->contains(*s1)) {
            s1->setStyle(newStyleId);
            hitSome = true;
          }
        }
    }
    aux.removeStroke(0);
    return hitSome;
  }

  // rect fill

  if (fillAreas)
#ifndef NEW_REGION_FILL
    for (UINT i = 0; i < m_regions.size(); i++) {
      int index, j = 0;

      do index = m_regions[i]->getEdge(j++)->m_index;
      while (index < 0 && j < (int)m_regions[i]->getEdgeCount());
      // if index<0, means that the region is purely of autoclose strokes!
      if (m_insideGroup != TGroupId() && index >= 0 &&
          !m_insideGroup.isParentOf(m_strokes[index]->m_groupId))
        continue;
      if (!onlyUnfilled || m_regions[i]->getStyle() == 0)
        hitSome |= m_regions[i]->selectFill(selArea, newStyleId);
    }
#else

    findRegions(selArea);

  for (UINT i = 0; i < m_regions.size(); i++) {
    if (m_insideGroup != TGroupId() &&
        !m_insideGroup.isParentOf(
            m_strokes[m_regions[i]->getEdge(0)->m_index]->m_groupId))
      continue;
    if (!onlyUnfilled || m_regions[i]->getStyle() == 0)
      hitSome |= m_regions[i]->selectFill(selArea, newStyleId);
  }
#endif

  if (fillLines)
    for (UINT i = 0; i < m_strokes.size(); i++) {
      if (!inCurrentGroup(i)) continue;

      TStroke *s = m_strokes[i]->m_s;

      if ((!onlyUnfilled || s->getStyle() == 0) &&
          selArea.contains(s->getBBox())) {
        s->setStyle(newStyleId);
        hitSome = true;
      }
    }
  return hitSome;
}

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

/*
void  TVectorImageImp::seedFill()
{
  std::vector<TFillSeed>::iterator it;
  TRegion*r;

  TFillStyleP app =NULL;

  for (it=m_seeds.begin(); it!=m_seeds.end(); )
    if ((r=fill(it->m_p, new TColorStyle(it->m_fillStyle.getPointer()) ))!=NULL)
    {
      it->m_r = r;
      it = m_seeds.erase(it);  // i seed provengono da immagini vecchie. non
servono piu'.
    }
  m_areValidRegions=true;
}
*/
//-----------------------------------------------------------------------------
/*
void  TVectorImage::seedFill()
{
  m_imp->seedFill();

}
*/
//-----------------------------------------------------------------------------

void TVectorImage::notifyChangedStrokes(
    const std::vector<int> &strokeIndexArray,
    const std::vector<TStroke *> &oldStrokeArray, bool areFlipped) {
  std::vector<TStroke *> aux;

  /*
if (oldStrokeArray.empty())
{
  for (int i=0; i<(int)strokeIndexArray.size(); i++)
    {
          TStroke *s = getStroke(strokeIndexArray[i]);
    aux.push_back(s);
          }
m_imp->notifyChangedStrokes(strokeIndexArray, aux, areFlipped);
  }
else*/
  m_imp->notifyChangedStrokes(strokeIndexArray, oldStrokeArray, areFlipped);
}

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

void TVectorImage::notifyChangedStrokes(int strokeIndexArray,
                                        TStroke *oldStroke, bool isFlipped) {
  std::vector<int> app(1);
  app[0] = strokeIndexArray;

  std::vector<TStroke *> oldStrokeArray(1);
  oldStrokeArray[0] = oldStroke ? oldStroke : getStroke(strokeIndexArray);
  m_imp->notifyChangedStrokes(app, oldStrokeArray, isFlipped);
}

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

// ofstream of("C:\\temp\\butta.txt");

void transferColors(const std::list<TEdge *> &oldList,
                    const std::list<TEdge *> &newList, bool isStrokeChanged,
                    bool isFlipped, bool overwriteColor) {
  if (newList.empty() || oldList.empty()) return;

  std::list<TEdge *>::const_iterator it;
// unused variable
#if 0 
list<TEdge*>::const_iterator it1;
#endif
  double totLength;
  if (isStrokeChanged) totLength = newList.front()->m_s->getLength();
  for (it = newList.begin(); it != newList.end(); ++it) {
    int newStyle = -1;  // ErrorStyle;
// unused variable
#if 0 
  int styleId = (*it)->m_styleId;
#endif
    if (!overwriteColor && (*it)->m_styleId != 0) continue;
    bool reversed;
    double deltaMax = 0.005;
    double l0, l1;
    if ((*it)->m_w0 > (*it)->m_w1) {
      reversed = !isFlipped;
      if (isStrokeChanged) {
        l0 = (*it)->m_s->getLength((*it)->m_w1) / totLength;
        l1 = (*it)->m_s->getLength((*it)->m_w0) / totLength;
      } else {
        l0 = (*it)->m_w1;
        l1 = (*it)->m_w0;
      }
    } else {
      reversed = isFlipped;
      if (isStrokeChanged) {
        l0 = (*it)->m_s->getLength((*it)->m_w0) / totLength;
        l1 = (*it)->m_s->getLength((*it)->m_w1) / totLength;
      } else {
        l0 = (*it)->m_w0;
        l1 = (*it)->m_w1;
      }
      // w0 = (*it)->m_w0;
      // w1 = (*it)->m_w1;
    }

    std::list<TEdge *>::const_iterator it1 = oldList.begin();
    for (; it1 != oldList.end(); ++it1) {
// unused variable
#if 0
      TEdge*e = *it1;
#endif
      if (/*(*it1)->m_styleId==0 ||*/
          (reversed && (*it1)->m_w0 < (*it1)->m_w1) ||
          (!reversed && (*it1)->m_w0 > (*it1)->m_w1))
        continue;
      double _l0, _l1;
      if (isStrokeChanged) {
        double totLength1 = (*it1)->m_s->getLength();

        _l0 = (*it1)->m_s->getLength(std::min((*it1)->m_w0, (*it1)->m_w1)) /
              totLength1;
        _l1 = (*it1)->m_s->getLength(std::max((*it1)->m_w0, (*it1)->m_w1)) /
              totLength1;
      } else {
        _l0 = std::min((*it1)->m_w0, (*it1)->m_w1);
        _l1 = std::max((*it1)->m_w0, (*it1)->m_w1);
      }
      double delta = std::min(l1, _l1) - std::max(l0, _l0);
      if (delta > deltaMax) {
        deltaMax = delta;
        newStyle = (*it1)->m_styleId;
      }
    }
    if (newStyle >= 0)  // !=ErrorStyle)
    {
      if ((*it)->m_r)
        (*it)->m_r->setStyle(newStyle);
      else
        (*it)->m_styleId = newStyle;
    }
  }
}

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

void TVectorImage::transferStrokeColors(TVectorImageP sourceImage,
                                        int sourceStroke,
                                        TVectorImageP destinationImage,
                                        int destinationStroke) {
  std::list<TEdge *> *sourceList =
      &(sourceImage->m_imp->m_strokes[sourceStroke]->m_edgeList);
  std::list<TEdge *> *destinationList =
      &(destinationImage->m_imp->m_strokes[destinationStroke]->m_edgeList);
  transferColors(*sourceList, *destinationList, true, false, false);
}

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

bool TVectorImage::Imp::areWholeGroups(const std::vector<int> &indexes) const {
  UINT i, j;
  for (i = 0; i < indexes.size(); i++) {
    if (m_strokes[indexes[i]]->m_isNewForFill) return false;
    if (!m_strokes[indexes[i]]->m_groupId.isGrouped()) return false;
    for (j = 0; j < m_strokes.size(); j++) {
      int ret = areDifferentGroup(indexes[i], false, j, false);
      if (ret == -1 || (ret >= 1 && find(indexes.begin(), indexes.end(), j) ==
                                        indexes.end()))
        return false;
    }
  }
  return true;
}

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

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

void TVectorImage::Imp::notifyChangedStrokes(
    const std::vector<int> &strokeIndexArray,
    const std::vector<TStroke *> &oldStrokeArray, bool areFlipped) {
#ifdef _DEBUG
  checkIntersections();
#endif

  assert(oldStrokeArray.empty() ||
         strokeIndexArray.size() == oldStrokeArray.size());

  if (!m_computedAlmostOnce && !m_notIntersectingStrokes) return;

  typedef std::list<TEdge *> EdgeList;
  std::vector<EdgeList> oldEdgeListArray(strokeIndexArray.size());
  int i;

  // se si sono trasformati  interi gruppi (senza deformare le stroke) non c'e'
  // bisogno di ricalcolare le regioni!
  if (oldStrokeArray.empty() && areWholeGroups(strokeIndexArray)) {
    m_areValidRegions = true;
    for (i = 0; i < (int)m_regions.size(); i++)
      invalidateRegionPropAndBBox(m_regions[i]);
    return;
  }

  QMutexLocker sl(m_mutex);
  for (i = 0; i < (int)strokeIndexArray.size(); i++)  // ATTENZIONE! non si puo'
                                                      // fare eraseIntersection
                                                      // in questo stesso ciclo
  {
    VIStroke *s = m_strokes[strokeIndexArray[i]];
    // if (s->m_s->isSelfLoop())
    //  assert(s->m_edgeList.size()<=1);

    std::list<TEdge *>::iterator it = s->m_edgeList.begin();
    for (; it != s->m_edgeList.end(); it++) {
      TEdge *e = new TEdge(**it, false);
      if (!oldStrokeArray.empty()) e->m_s = oldStrokeArray[i];
      oldEdgeListArray[i].push_back(e);  // bisogna allocare nuovo edge,
                                         // perche'la eraseIntersection poi lo
                                         // cancella....
      if ((*it)->m_toBeDeleted) delete *it;
    }
    s->m_edgeList.clear();
  }

  for (i = 0; i < (int)strokeIndexArray.size(); i++) {
    eraseIntersection(strokeIndexArray[i]);
    if (!m_notIntersectingStrokes)
      m_strokes[strokeIndexArray[i]]->m_isNewForFill = true;
  }

  computeRegions();  // m_imp->m_strokes, m_imp->m_regions);

  for (i = 0; i < (int)strokeIndexArray.size(); i++) {
    transferColors(oldEdgeListArray[i],
                   m_strokes[strokeIndexArray[i]]->m_edgeList, true, areFlipped,
                   false);
    clearPointerContainer(oldEdgeListArray[i]);
  }

#ifdef _DEBUG
  checkIntersections();
#endif
}

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

void TVectorImage::findRegions() {
  // for (int i=0; i<(int)m_imp->m_strokes.size(); i++)
  //  {
  //  m_imp->eraseIntersection(i);
  //	  m_imp->m_strokes[i]->m_isNewForFill=true;
  //  }

  if (m_imp->m_areValidRegions) return;

  // m_imp->m_regions.clear();

  // compute regions...
  m_imp->computeRegions();  // m_imp->m_strokes, m_imp->m_regions);
}

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

void TVectorImage::putRegion(TRegion *region) {
  m_imp->m_regions.push_back(region);
}

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

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

void TVectorImage::Imp::cloneRegions(TVectorImage::Imp &out,
                                     bool doComputeRegions) {
  std::unique_ptr<IntersectionBranch[]> v;
  UINT size = getFillData(v);
  out.setFillData(v, size, doComputeRegions);
}

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

TVectorImageP TVectorImage::clone() const {
  return TVectorImageP(cloneImage());
}

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

TImage *TVectorImage::cloneImage() const {
  TVectorImage *out = new TVectorImage;

  out->m_imp->m_autocloseTolerance = m_imp->m_autocloseTolerance;
  out->m_imp->m_maxGroupId         = m_imp->m_maxGroupId;
  out->m_imp->m_maxGhostGroupId    = m_imp->m_maxGhostGroupId;

  for (int i = 0; i < (int)m_imp->m_strokes.size(); i++) {
    out->m_imp->m_strokes.push_back(new VIStroke(*(m_imp->m_strokes[i])));
    out->m_imp->m_strokes.back()->m_s->setId(m_imp->m_strokes[i]->m_s->getId());
  }

  m_imp->cloneRegions(*out->m_imp);

  out->setPalette(getPalette());
  out->m_imp->m_computedAlmostOnce = m_imp->m_computedAlmostOnce;
  out->m_imp->m_justLoaded         = m_imp->m_justLoaded;

  return out;
}

//-----------------------------------------------------------------------------
/*
TVectorImageP mergeAndClear(TVectorImageP v1, TVectorImageP v2 )
{
  TVectorImageP out = new TVectorImage;

  std::vector<VIStroke*>::iterator it_b =  v1->m_imp->m_strokes.begin();
  std::vector<VIStroke*>::iterator it_e =  v1->m_imp->m_strokes.end();

  std::copy( it_b, it_e, std::back_inserter( out->m_imp->m_strokes ) );

  it_b =  v2->m_imp->m_strokes.begin();
  it_e =  v2->m_imp->m_strokes.end();

  std::copy( it_b, it_e, std::back_inserter( out->m_imp->m_strokes ) );

  v1->m_imp->m_regions.clear();
  v1->m_imp->m_strokes.clear();
  v2->m_imp->m_regions.clear();
  v2->m_imp->m_strokes.clear();

  out->m_imp->m_areValidRegions = false;
  return out;
}
*/

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

VIStroke::VIStroke(const VIStroke &s, bool sameId)
    : m_isPoint(s.m_isPoint)
    , m_isNewForFill(s.m_isNewForFill)
    , m_groupId(s.m_groupId) {
  m_s                                     = new TStroke(*s.m_s);
  std::list<TEdge *>::const_iterator it   = s.m_edgeList.begin(),
                                     it_e = s.m_edgeList.end();
  for (; it != it_e; ++it) {
    m_edgeList.push_back(new TEdge(**it, true));
    m_edgeList.back()->m_s = m_s;
  }
  if (sameId) m_s->setId(s.m_s->getId());
}

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

int TVectorImage::mergeImage(const TVectorImageP &img, const TAffine &affine,
                             bool sameStrokeId) {
  QMutexLocker sl(m_imp->m_mutex);

#ifdef _DEBUG
  checkIntersections();
#endif

  TPalette *tarPlt = getPalette();
  TPalette *srcPlt = img->getPalette();

  assert(tarPlt);
  assert(tarPlt->getPageCount() > 0);

  // merge della palette
  std::map<int, int> styleTable;
  std::set<int> usedStyles;
  img->getUsedStyles(usedStyles);

  // gmt, 16/10/07. Quando si copia e incolla un path su uno stroke succede
  // che la palette dell'immagine sorgente sia vuota. Non mi sembra sbagliato
  // mettere comunque un test qui
  if (srcPlt) mergePalette(tarPlt, styleTable, srcPlt, usedStyles);

  return mergeImage(img, affine, styleTable, sameStrokeId);
}

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

int TVectorImage::mergeImage(const TVectorImageP &img, const TAffine &affine,
                             const std::map<int, int> &styleTable,
                             bool sameStrokeId) {
  int imageSize = img->getStrokeCount();
  if (imageSize == 0) return 0;
  QMutexLocker sl(m_imp->m_mutex);

  m_imp->m_computedAlmostOnce |= img->m_imp->m_computedAlmostOnce;

  std::vector<int> changedStrokeArray(imageSize);

  img->m_imp->reindexGroups(*m_imp);

  int i;
  int insertAt = 0;

  if (m_imp->m_insideGroup !=
      TGroupId())  // if is inside a group, new image is put in that group.
  {
    TGroupId groupId;
    for (i = m_imp->m_strokes.size() - 1; i >= 0; i--) {
      if (m_imp->m_insideGroup.isParentOf(m_imp->m_strokes[i]->m_groupId)) {
        insertAt = i + 1;
        groupId  = m_imp->m_strokes[i]->m_groupId;
        break;
      }
    }
    if (insertAt != 0) {
      for (i = 0; i < (int)img->m_imp->m_strokes.size(); i++) {
        if (!img->m_imp->m_strokes[i]->m_groupId.isGrouped()) {
          img->m_imp->m_strokes[i]->m_groupId = groupId;
        } else {
          img->m_imp->m_strokes[i]->m_groupId =
              TGroupId(groupId, img->m_imp->m_strokes[i]->m_groupId);
        }
      }
    }
  }

  // si fondono l'ultimo gruppo ghost della vecchia a e il primo della nuova

  else if (!m_imp->m_strokes.empty() &&
           m_imp->m_strokes.back()->m_groupId.isGrouped(true) != 0 &&
           img->m_imp->m_strokes[0]->m_groupId.isGrouped(true) != 0) {
    TGroupId idNew = m_imp->m_strokes.back()->m_groupId,
             idOld = img->m_imp->m_strokes[0]->m_groupId;
    for (i = 0; i < (int)img->m_imp->m_strokes.size() &&
                img->m_imp->m_strokes[i]->m_groupId == idOld;
         i++)
      img->m_imp->m_strokes[i]->m_groupId = idNew;
  }

  // merge dell'immagine
  std::map<int, int>::const_iterator styleTableIt;
  int oldSize = getStrokeCount();

  for (i = 0; i < imageSize; i++) {
    VIStroke *srcStroke = img->m_imp->m_strokes[i];
    VIStroke *tarStroke = new VIStroke(*srcStroke, sameStrokeId);

    int styleId;
    // cambio i colori delle regioni
    std::list<TEdge *>::const_iterator it   = tarStroke->m_edgeList.begin(),
                                       it_e = tarStroke->m_edgeList.end();
    for (; it != it_e; ++it) {
      int styleId  = (*it)->m_styleId;
      styleTableIt = styleTable.find(styleId);
      assert(styleTableIt != styleTable.end());
      if (styleTableIt != styleTable.end())
        (*it)->m_styleId = styleTableIt->second;
    }

    tarStroke->m_s->transform(affine, true);
    int strokeId = srcStroke->m_s->getId();
    if (getStrokeById(strokeId) == 0) tarStroke->m_s->setId(strokeId);

    // cambio i colori dello stroke
    styleId      = srcStroke->m_s->getStyle();
    styleTableIt = styleTable.find(styleId);
    assert(styleTableIt != styleTable.end());
    if (styleTableIt != styleTable.end())
      tarStroke->m_s->setStyle(styleTableIt->second);
    if (insertAt == 0) {
      m_imp->m_strokes.push_back(tarStroke);
      changedStrokeArray[i] = oldSize + i;
    } else {
      std::vector<VIStroke *>::iterator it = m_imp->m_strokes.begin();
      advance(it, insertAt + i);
      m_imp->m_strokes.insert(it, tarStroke);
      changedStrokeArray[i] = insertAt + i;
    }
  }
  if (insertAt > 0) {
    // for (i=changedStrokeArray.back()+1; i<m_imp->m_strokes.size(); i++)
    //  changedStrokeArray.push_back(i);
    m_imp->reindexEdges(changedStrokeArray, true);
  }

  notifyChangedStrokes(changedStrokeArray, std::vector<TStroke *>(), false);

#ifdef _DEBUG
  checkIntersections();
#endif

  return insertAt;
}

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

void TVectorImage::Imp::reindexGroups(TVectorImage::Imp &img) {
  UINT i, j;
  int newMax      = img.m_maxGroupId;
  int newMaxGhost = img.m_maxGhostGroupId;
  for (i = 0; i < m_strokes.size(); i++) {
    VIStroke *s = m_strokes[i];
    if (s->m_groupId.m_id.empty()) continue;
    if (s->m_groupId.m_id[0] > 0)
      for (j = 0; j < s->m_groupId.m_id.size(); j++) {
        s->m_groupId.m_id[j] += img.m_maxGroupId;
        newMax = std::max(newMax, s->m_groupId.m_id[j]);
      }
    else
      for (j = 0; j < s->m_groupId.m_id.size(); j++) {
        s->m_groupId.m_id[j] -= img.m_maxGhostGroupId;
        newMaxGhost = std::max(newMaxGhost, -s->m_groupId.m_id[j]);
      }
  }
  m_maxGroupId = img.m_maxGroupId = newMax;
  m_maxGhostGroupId = img.m_maxGhostGroupId = newMaxGhost;
}

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

void TVectorImage::mergeImage(const std::vector<const TVectorImage *> &images) {
  UINT oldSize = getStrokeCount();
  std::vector<int> changedStrokeArray;
  const TVectorImage *img;
  int index;

  if (m_imp->m_insideGroup != TGroupId()) {
    for (index = m_imp->m_strokes.size() - 1; index > -1; index--)
      if (m_imp->m_insideGroup.isParentOf(m_imp->m_strokes[index]->m_groupId))
        break;
    assert(index > -1);
  } else
    index = getStrokeCount() - 1;

  for (UINT j = 0; j < images.size(); ++j) {
    img = images[j];
    if (img->getStrokeCount() == 0) continue;

    img->m_imp->reindexGroups(*m_imp);

    int i = 0;
    /*if (!m_imp->m_strokes.empty() &&
m_imp->m_strokes[index-1]->m_groupId.isGrouped(true)!=0 &&
img->m_imp->m_strokes[0]->m_groupId.isGrouped(true)!=0)
{
assert(false);
TGroupId idNew = m_imp->m_strokes[index]->m_groupId, idOld =
img->m_imp->m_strokes[0]->m_groupId;
for  (;i<(int)img->m_imp->m_strokes.size() &&
img->m_imp->m_strokes[i]->m_groupId==idOld; i++)
img->m_imp->m_strokes[i]->m_groupId==idNew;
}*/

    int strokeCount = img->getStrokeCount();
    m_imp->m_computedAlmostOnce |= img->m_imp->m_computedAlmostOnce;
    for (i = 0; i < strokeCount; i++) {
      VIStroke *srcStroke = img->m_imp->m_strokes[i];
      VIStroke *tarStroke = new VIStroke(*srcStroke);
      int strokeId        = srcStroke->m_s->getId();
      if (getStrokeById(strokeId) == 0) tarStroke->m_s->setId(strokeId);

      index++;
      if (m_imp->m_insideGroup == TGroupId())
        m_imp->m_strokes.push_back(tarStroke);
      else  // if we are inside a group, the images must become part of that
            // group
      {
        tarStroke->m_groupId =
            TGroupId(m_imp->m_insideGroup, tarStroke->m_groupId);
        m_imp->insertStrokeAt(tarStroke, index);
      }

      changedStrokeArray.push_back(index);
    }
  }

  notifyChangedStrokes(changedStrokeArray, std::vector<TStroke *>(), false);
}
//-------------------------------------------------------------------

void TVectorImage::recomputeRegionsIfNeeded() {
  if (!m_imp->m_justLoaded) return;

  m_imp->m_justLoaded = false;

  std::vector<int> v(m_imp->m_strokes.size());
  int i;
  for (i = 0; i < (int)m_imp->m_strokes.size(); i++) v[i] = i;

  m_imp->notifyChangedStrokes(v, std::vector<TStroke *>(), false);
}

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

void TVectorImage::eraseStyleIds(const std::vector<int> styleIds) {
  int j;
  for (j = 0; j < (int)styleIds.size(); j++) {
    int styleId = styleIds[j];

    int strokeCount = getStrokeCount();
    int i;
    for (i = strokeCount - 1; i >= 0; i--) {
      TStroke *stroke = getStroke(i);
      if (stroke && stroke->getStyle() == styleId) removeStroke(i);
    }
    int regionCount = getRegionCount();
    for (i = 0; i < regionCount; i++) {
      TRegion *region = getRegion(i);
      if (!region || region->getStyle() != styleId) continue;
      TPointD p;
      if (region->getInternalPoint(p)) fill(p, 0);
    }
  }
}

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

void TVectorImage::insertImage(const TVectorImageP &img,
                               const std::vector<int> &dstIndices) {
  UINT i;
  UINT imageSize = img->getStrokeCount();
  assert(dstIndices.size() == imageSize);

  // img->m_imp->reindexGroups(*m_imp);
  std::vector<int> changedStrokeArray(imageSize);

  std::vector<VIStroke *>::iterator it = m_imp->m_strokes.begin();

  for (i = 0; i < imageSize; i++) {
    assert(i == 0 || dstIndices[i] > dstIndices[i - 1]);

    VIStroke *srcStroke = img->m_imp->m_strokes[i];
    VIStroke *tarStroke = new VIStroke(*srcStroke);
    int strokeId        = srcStroke->m_s->getId();
    if (getStrokeById(strokeId) == 0) tarStroke->m_s->setId(strokeId);
    advance(it, (i == 0) ? dstIndices[i] : dstIndices[i] - dstIndices[i - 1]);

    it = m_imp->m_strokes.insert(it, tarStroke);

    changedStrokeArray[i] = dstIndices[i];
  }
  m_imp->reindexEdges(changedStrokeArray, true);

  notifyChangedStrokes(changedStrokeArray, std::vector<TStroke *>(), false);
  // m_imp->computeRegions();
}

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

void TVectorImage::enableRegionComputing(bool enabled,
                                         bool notIntersectingStrokes) {
  m_imp->m_computeRegions         = enabled;
  m_imp->m_notIntersectingStrokes = notIntersectingStrokes;
}

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

void TVectorImage::enableMinimizeEdges(bool enabled) {
  m_imp->m_minimizeEdges = enabled;
}

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

TVectorImageP TVectorImage::splitImage(const std::vector<int> &indices,
                                       bool removeFlag) {
  TVectorImageP out             = new TVectorImage;
  out->m_imp->m_maxGroupId      = m_imp->m_maxGroupId;
  out->m_imp->m_maxGhostGroupId = m_imp->m_maxGhostGroupId;

  std::vector<int> toBeRemoved;

  TPalette *vp = getPalette();
  if (vp) out->setPalette(vp->clone());

  for (UINT i = 0; i < indices.size(); ++i) {
    VIStroke *ref = m_imp->m_strokes[indices[i]];
    assert(ref);
    VIStroke *vs       = new VIStroke(*ref);
    vs->m_isNewForFill = true;
    out->m_imp->m_strokes.push_back(vs);
  }

  if (removeFlag) removeStrokes(indices, true, true);
  out->m_imp->m_areValidRegions    = false;
  out->m_imp->m_computedAlmostOnce = m_imp->m_computedAlmostOnce;
  return out;
}

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

TVectorImageP TVectorImage::splitSelected(bool removeFlag) {
  TVectorImageP out = new TVectorImage;
  std::vector<int> toBeRemoved;

  for (UINT i = 0; i < getStrokeCount(); ++i) {
    VIStroke *ref = m_imp->m_strokes[i];
    assert(ref);
    if (ref->m_s->getFlag(TStroke::c_selected_flag)) {
      VIStroke *stroke = new VIStroke(*ref);
      out->m_imp->m_strokes.push_back(stroke);
      if (removeFlag) {
        toBeRemoved.push_back(i);
        //  	  removeStroke(i);
        //			delete ref;
        //  	  i--;
      }
    }
  }
  removeStrokes(toBeRemoved, true, true);
  out->m_imp->m_areValidRegions = false;
  return out;
}

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

void TVectorImage::validateRegions(bool state) {
  m_imp->m_areValidRegions = state;
}

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

/*
void TVectorImage::invalidateBBox()
{
  for(UINT i=0; i<getRegionCount(); i++)
    getRegion(i)->invalidateBBox();
}
*/
//-----------------------------------------------------------------------------

void TVectorImage::setFillData(std::unique_ptr<IntersectionBranch[]> const &v,
                               UINT branchCount, bool doComputeRegions) {
  m_imp->setFillData(v, branchCount, doComputeRegions);
}

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

UINT TVectorImage::getFillData(std::unique_ptr<IntersectionBranch[]> &v) {
  return m_imp->getFillData(v);
}

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

void TVectorImage::enableStrokeStyle(int index, bool enable) {
  DisabledStrokeStyles &disabledSet = getDisabledStrokeStyleSet();
  if (enable)
    disabledSet.erase(index);
  else
    disabledSet.insert(index);
}

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

bool TVectorImage::isStrokeStyleEnabled(int index) {
  return isStrokeStyleEnabled__(index);
}

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

void TVectorImage::getUsedStyles(std::set<int> &styles) const {
  UINT strokeCount = getStrokeCount();
  UINT i           = 0;
  for (; i < strokeCount; ++i) {
    VIStroke *srcStroke = m_imp->m_strokes[i];
    int styleId         = srcStroke->m_s->getStyle();
    if (styleId != 0) styles.insert(styleId);
    std::list<TEdge *>::const_iterator it = srcStroke->m_edgeList.begin();
    for (; it != srcStroke->m_edgeList.end(); ++it) {
      styleId = (*it)->m_styleId;
      if (styleId != 0) styles.insert(styleId);
    }
  }
}

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

inline double recomputeW1(double oldW, const TStroke &oldStroke,
                          const TStroke &newStroke, double startW) {
  double oldLength = oldStroke.getLength();
  double newLength = newStroke.getLength();

  assert(startW <= oldW);
  assert(newLength < oldLength);

  double s = oldStroke.getLength(startW, oldW);
  assert(s <= newLength || areAlmostEqual(s, newLength, 1e-5));

  return newStroke.getParameterAtLength(s);
}

//-----------------------------------------------------------------------------
inline double recomputeW2(double oldW, const TStroke &oldStroke,
                          const TStroke &newStroke, double length) {
  double s = oldStroke.getLength(oldW);
  return newStroke.getParameterAtLength(length + s);
}

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

inline double recomputeW(double oldW, const TStroke &oldStroke,
                         const TStroke &newStroke, bool isAtBegin) {
  double oldLength = oldStroke.getLength();
  double newLength = newStroke.getLength();

  assert(newLength < oldLength);
  double s =
      oldStroke.getLength(oldW) - ((isAtBegin) ? 0 : oldLength - newLength);
  assert(s <= newLength || areAlmostEqual(s, newLength, 1e-5));

  return newStroke.getParameterAtLength(s);
}

//-----------------------------------------------------------------------------
#ifdef _DEBUG
void TVectorImage::checkIntersections() { m_imp->checkIntersections(); }
#endif

/*
void TVectorImage::reassignStyles()
{
  set<int> styles;
  UINT  strokeCount = getStrokeCount();
  UINT i=0;

  for( ; i< strokeCount; ++i)
    {
     int styleId = getStroke(i)->getStyle();
     if(styleId != 0) styles.insert(styleId);
    }
  UINT regionCount = getRegionCount();
  for( i = 0; i< regionCount; ++i)
    {
     int styleId = getRegion(i)->getStyle();
     if(styleId != 0) styles.insert(styleId);
    }

  map<int, int> conversionTable;
  for(set<int>::iterator it = styles.begin(); it != styles.end(); ++it)
    {
     int styleId = *it;
     conversionTable[styleId] = styleId + 13;
    }

  for( i = 0; i< strokeCount; ++i)
    {
     TStroke *stroke = getStroke(i);
     int styleId = stroke->getStyle();
     if(styleId != 0)
       {
        map<int, int>::iterator it = conversionTable.find(styleId);
        if(it != conversionTable.end())
          stroke->setStyle(it->second);
       }
    }

  for( i = 0; i< regionCount; ++i)
    {
     TRegion *region = getRegion(i);
     int styleId = region->getStyle();
     if(styleId != 0)
       {
        map<int, int>::iterator it = conversionTable.find(styleId);
        if(it != conversionTable.end())
          region->setStyle(it->second);
       }
    }
}

*/

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

bool TVectorImage::isComputedRegionAlmostOnce() const {
  return m_imp->m_computedAlmostOnce;
}

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

void TVectorImage::splitStroke(int strokeIndex,
                               const std::vector<DoublePair> &sortedWRanges) {
  m_imp->splitStroke(strokeIndex, sortedWRanges);
}

void TVectorImage::Imp::splitStroke(
    int strokeIndex, const std::vector<DoublePair> &sortedWRanges) {
  int i;
  VIStroke *subV = 0;

  if (strokeIndex >= (int)m_strokes.size() || sortedWRanges.empty()) return;

  VIStroke *vs     = m_strokes[strokeIndex];
  TGroupId groupId = vs->m_groupId;

  // se e' un self loop, alla fine non lo sara', e deve stare insieme
  // alle stroke non loopate. sposto lo stroke se serve
  /*
{
if (vs->m_s->isSelfLoop())
int up = strokeIndex+1;
while (up<(int)m_strokes.size() && m_strokes[up]->m_s->isSelfLoop())
up++;
int dn = strokeIndex-1;
while (dn>=0 && m_strokes[dn]->m_s->isSelfLoop())
dn--;
if ((up == m_strokes.size() || up!=strokeIndex+1) && (dn<0 ||
dn!=strokeIndex-1))
{
if (up>=(int)m_strokes.size())
{
assert(dn>=0);
moveStroke(strokeIndex, dn+1);
strokeIndex = dn+1;
}
else
{
moveStroke(strokeIndex, up-1);
strokeIndex = up-1;
}
}
}
*/
  assert(vs == m_strokes[strokeIndex]);

  bool toBeJoined =
      (vs->m_s->isSelfLoop() && sortedWRanges.front().first == 0.0 &&
       sortedWRanges.back().second == 1.0);

  int styleId = vs->m_s->getStyle();
  TStroke::OutlineOptions oOptions(vs->m_s->outlineOptions());

  m_regions.clear();

  std::list<TEdge *> origEdgeList;  // metto al pizzo la edge std::list della
                                    // stroke, perche' la erase intersection ne
                                    // fara' scempio
  std::list<TEdge *>::iterator it   = vs->m_edgeList.begin(),
                               it_e = vs->m_edgeList.end();
  for (; it != it_e; ++it) origEdgeList.push_back(new TEdge(**it, false));

  removeStroke(strokeIndex, false);

  std::vector<std::list<TEdge *>> edgeList(sortedWRanges.size());
  strokeIndex--;

  int wSize = (int)sortedWRanges.size();

  for (i = 0; i < wSize; i++) {
    assert(sortedWRanges[i].first < sortedWRanges[i].second);
    assert(i == wSize - 1 ||
           sortedWRanges[i].second <= sortedWRanges[i + 1].first);
    assert(sortedWRanges[i].first >= 0 && sortedWRanges[i].first <= 1);
    assert(sortedWRanges[i].second >= 0 && sortedWRanges[i].second <= 1);

    subV = new VIStroke(new TStroke(), groupId);
    TStroke s, dummy;

    if (areAlmostEqual(sortedWRanges[i].first, 0, 1e-4))
      s = *vs->m_s;
    else
      vs->m_s->split(sortedWRanges[i].first, dummy, s);

    double lenAtW0 = vs->m_s->getLength(sortedWRanges[i].first);
    double lenAtW1 = vs->m_s->getLength(sortedWRanges[i].second);
    double newW1   = s.getParameterAtLength(lenAtW1 - lenAtW0);

    if (areAlmostEqual(newW1, 1.0, 1e-4))
      *(subV->m_s) = s;
    else
      s.split(newW1, *(subV->m_s), dummy);

    strokeIndex++;
    /*assert(m_strokes[strokeIndex]->m_edgeList.empty());
assert(m_strokes[strokeIndex-wSize+1]->m_edgeList.empty());*/

    std::list<TEdge *>::const_iterator it   = origEdgeList.begin(),
                                       it_e = origEdgeList.end();
    for (; it != it_e; ++it) {
      double wMin = std::min((*it)->m_w0, (*it)->m_w1);
      double wMax = std::max((*it)->m_w0, (*it)->m_w1);

      if (wMin >= sortedWRanges[i].second || wMax <= sortedWRanges[i].first)
        continue;

      TEdge *e = new TEdge(**it, false);
      if (wMin < sortedWRanges[i].first)
        wMin = 0.0;
      else
        wMin =
            recomputeW1(wMin, *(vs->m_s), *(subV->m_s), sortedWRanges[i].first);

      if (wMax > sortedWRanges[i].second)
        wMax = 1.0;
      else
        wMax =
            recomputeW1(wMax, *(vs->m_s), *(subV->m_s), sortedWRanges[i].first);

      if (e->m_w0 < e->m_w1)
        e->m_w0 = wMin, e->m_w1 = wMax;
      else
        e->m_w1 = wMin, e->m_w0 = wMax;
      e->m_r     = 0;
      e->m_s     = subV->m_s;
      e->m_index = strokeIndex;
      edgeList[i].push_back(e);
    }
    subV->m_edgeList.clear();
    insertStrokeAt(subV, strokeIndex);
    subV->m_s->setStyle(styleId);
    subV->m_s->outlineOptions() = oOptions;
  }

  clearPointerContainer(origEdgeList);

  if (toBeJoined)  // la stroke e' un loop, quindi i due choncketti iniziali e
                   // finali vanno joinati
  {
    VIStroke *s0           = m_strokes[strokeIndex];
    VIStroke *s1           = m_strokes[strokeIndex - wSize + 1];
    std::list<TEdge *> &l0 = edgeList.back();
    std::list<TEdge *> &l1 = edgeList.front();

    // assert(s0->m_edgeList.empty());
    // assert(s1->m_edgeList.empty());
    removeStroke(strokeIndex - wSize + 1, false);

    strokeIndex--;
    removeStroke(strokeIndex, false);

    VIStroke *s = new VIStroke(joinStrokes(s0->m_s, s1->m_s), groupId);
    insertStrokeAt(s, strokeIndex);

    std::list<TEdge *>::iterator it = l0.begin(), it_e = l0.end();
    for (; it != it_e; ++it) {
      (*it)->m_s     = s->m_s;
      (*it)->m_index = strokeIndex;
      (*it)->m_w0    = recomputeW2((*it)->m_w0, *(s0->m_s), *(s->m_s), 0);
      (*it)->m_w1    = recomputeW2((*it)->m_w1, *(s0->m_s), *(s->m_s), 0);
    }
    it            = l1.begin();
    double length = s0->m_s->getLength();
    while (it != l1.end()) {
      (*it)->m_s     = s->m_s;
      (*it)->m_index = strokeIndex;
      (*it)->m_w0    = recomputeW2((*it)->m_w0, *(s1->m_s), *(s->m_s), length);
      (*it)->m_w1    = recomputeW2((*it)->m_w1, *(s1->m_s), *(s->m_s), length);
      l0.push_back(*it);
      it = l1.erase(it);
    }
    assert(l1.empty());
    edgeList.erase(edgeList.begin());

    std::vector<DoublePair> appSortedWRanges;

    wSize--;

    delete s0;
    delete s1;
  }

  // checkIntersections();

  // double len  = e->m_s->getLength();
  // if (recomputeRegions)

  if (m_computedAlmostOnce) {
    computeRegions();
    assert((int)edgeList.size() == wSize);
    assert((int)m_strokes.size() > strokeIndex);

    for (i = 0; i < wSize; i++)
      transferColors(edgeList[i],
                     m_strokes[strokeIndex - wSize + i + 1]->m_edgeList, false,
                     false, false);
  }

  for (i = 0; i < wSize; i++) clearPointerContainer(edgeList[i]);

  delete vs;
}

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

static void computeEdgeList(TStroke *newS, const std::list<TEdge *> &edgeList1,
                            bool join1AtBegin,
                            const std::list<TEdge *> &edgeList2,
                            bool join2AtBegin, std::list<TEdge *> &edgeList) {
  std::list<TEdge *>::const_iterator it;

  if (!edgeList1.empty()) {
    TStroke *s1    = edgeList1.front()->m_s;
    double length1 = s1->getLength();
    ;

    for (it = edgeList1.begin(); it != edgeList1.end(); ++it) {
      double l0 = s1->getLength((*it)->m_w0), l1 = s1->getLength((*it)->m_w1);
      if (join1AtBegin) l0 = length1 - l0, l1 = length1 - l1;

      TEdge *e         = new TEdge();
      e->m_toBeDeleted = true;
      e->m_index       = -1;
      e->m_s           = newS;
      e->m_styleId     = (*it)->m_styleId;
      e->m_w0          = newS->getParameterAtLength(l0);
      e->m_w1          = newS->getParameterAtLength(l1);
      edgeList.push_back(e);
    }
  }

  if (!edgeList2.empty()) {
    TStroke *s2    = edgeList2.front()->m_s;
    double offset  = newS->getLength(newS->getW(s2->getPoint(0.0)));
    double length2 = s2->getLength();
    for (it = edgeList2.begin(); it != edgeList2.end(); ++it) {
      double l0 = s2->getLength((*it)->m_w0), l1 = s2->getLength((*it)->m_w1);
      if (!join2AtBegin) l0 = length2 - l0, l1 = length2 - l1;

      TEdge *e         = new TEdge();
      e->m_toBeDeleted = true;
      e->m_index       = -1;
      e->m_s           = newS;
      e->m_styleId     = (*it)->m_styleId;
      e->m_w0          = newS->getParameterAtLength(offset + l0);
      e->m_w1          = newS->getParameterAtLength(offset + l1);
      edgeList.push_back(e);
    }
  }
}

//-----------------------------------------------------------------------------
#ifdef _DEBUG

// #include "tpalette.h"
#include "tcolorstyles.h"

void printEdges(std::ofstream &os, char *str, TPalette *plt,
                const std::list<TEdge *> &edges) {
  std::list<TEdge *>::const_iterator it;

  os << str << std::endl;

  for (it = edges.begin(); it != edges.end(); ++it) {
    TColorStyle *style = plt->getStyle((*it)->m_styleId);
    TPixel32 color     = style->getMainColor();
    os << "w0-w1:(" << (*it)->m_w0 << "-->" << (*it)->m_w1 << ")" << std::endl;
    os << "color=(" << color.r << "," << color.g << "," << color.b << ")"
       << std::endl;
  }
  os << std::endl << std::endl << std::endl;
}
#else
#define printEdges

#endif
//-----------------------------------------------------------------------------

#ifdef _DEBUG
void TVectorImage::Imp::printStrokes(std::ofstream &os) {
  for (int i = 0; i < (int)m_strokes.size(); i++) {
    os << "*****stroke #" << i << " *****";
    m_strokes[i]->m_s->print(os);
  }
}

#endif

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

TStroke *TVectorImage::removeEndpoints(int strokeIndex, double *offset) {
  return m_imp->removeEndpoints(strokeIndex, offset);
}

void TVectorImage::restoreEndpoints(int index, TStroke *oldStroke, double offset) {
  m_imp->restoreEndpoints(index, oldStroke, offset);
}

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

VIStroke *TVectorImage::Imp::extendStrokeSmoothly(int index,
                                                  const TThickPoint &pos,
                                                  int cpIndex) {
  TStroke *stroke  = m_strokes[index]->m_s;
  TGroupId groupId = m_strokes[index]->m_groupId;

  int cpCount = stroke->getControlPointCount();
  int styleId = stroke->getStyle();
  const TThickQuadratic *q =
      stroke->getChunk(cpIndex == 0 ? 0 : stroke->getChunkCount() - 1);

  double len    = q->getLength();
  double w      = exp(-len * 0.01);
  TThickPoint m = q->getThickP1();

  TThickPoint p1 =
      (cpIndex == 0 ? q->getThickP0() : q->getThickP2()) * (1 - w) + m * w;
  TThickPoint middleP = (p1 + pos) * 0.5;

  double angle = fabs(cross(normalize(m - middleP), normalize(pos - middleP)));
  if (angle < 0.05) middleP = (m + pos) * 0.5;

  stroke->setControlPoint(cpIndex, middleP);
  if (isAlmostZero(len)) {
    if (cpIndex == 0)
      stroke->setControlPoint(1,
                              middleP * 0.1 + stroke->getControlPoint(2) * 0.9);
    else
      stroke->setControlPoint(
          cpCount - 2,
          middleP * 0.1 + stroke->getControlPoint(cpCount - 3) * 0.9);
  }

  std::vector<TThickPoint> points(cpCount);
  for (int i = 0; i < cpCount - 1; i++)
    points[i] = stroke->getControlPoint((cpIndex == 0) ? cpCount - i - 1 : i);
  points[cpCount - 1] = pos;

  TStroke *newStroke = new TStroke(points);
  newStroke->setStyle(styleId);
  newStroke->outlineOptions() = stroke->outlineOptions();
  std::list<TEdge *> oldEdgeList, emptyList;
  computeEdgeList(newStroke, m_strokes[index]->m_edgeList, cpIndex == 0,
                  emptyList, 0, oldEdgeList);

  std::vector<int> toBeDeleted;
  toBeDeleted.push_back(index);
  removeStrokes(toBeDeleted, true, false);

  insertStrokeAt(new VIStroke(newStroke, groupId), index, false);
  computeRegions();
  transferColors(oldEdgeList, m_strokes[index]->m_edgeList, true, false, true);

  return m_strokes[index];
}

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

VIStroke *TVectorImage::Imp::extendStroke(int index, const TThickPoint &p,
                                          int cpIndex) {
  TGroupId groupId = m_strokes[index]->m_groupId;

  TStroke *stroke = m_strokes[index]->m_s;

  TStroke *ret;
  int cpCount = stroke->getControlPointCount();
  int count   = 0;
  std::vector<TThickPoint> points(cpCount + 2);
  int i, incr = (cpIndex == 0) ? -1 : 1;
  for (i = ((cpIndex == 0) ? cpCount - 1 : 0); i != cpIndex + incr; i += incr)
    points[count++] = stroke->getControlPoint(i);
  TThickPoint tp(p, points[count - 1].thick);
  points[count++] = 0.5 * (stroke->getControlPoint(cpIndex) + tp);
  points[count++] = tp;

  TStroke *newStroke = new TStroke(points);
  newStroke->setStyle(stroke->getStyle());
  newStroke->outlineOptions() = stroke->outlineOptions();
  ret                         = newStroke;
  std::list<TEdge *> oldEdgeList, emptyList;

  if (m_computedAlmostOnce)
    computeEdgeList(newStroke, m_strokes[index]->m_edgeList, cpIndex == 0,
                    emptyList, false, oldEdgeList);

  std::vector<int> toBeDeleted;
  toBeDeleted.push_back(index);
  removeStrokes(toBeDeleted, true, false);

  // removeStroke(index, false);

  insertStrokeAt(new VIStroke(newStroke, groupId), index, false);

  if (m_computedAlmostOnce) {
    computeRegions();
    transferColors(oldEdgeList, m_strokes[index]->m_edgeList, true, false,
                   true);
  }
  return m_strokes[index];
}

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

VIStroke *TVectorImage::Imp::joinStroke(int index1, int index2, int cpIndex1,
                                        int cpIndex2) {
  assert(m_strokes[index1]->m_groupId == m_strokes[index2]->m_groupId);

  TGroupId groupId = m_strokes[index1]->m_groupId;

  TStroke *stroke1 = m_strokes[index1]->m_s;
  TStroke *stroke2 = m_strokes[index2]->m_s;
  // TStroke* ret;
  int cpCount1 = stroke1->getControlPointCount();
  int cpCount2 = stroke2->getControlPointCount();
  int styleId  = stroke1->getStyle();

  // check if the both ends are at the same position
  bool isSamePos = isAlmostZero(tdistance2(stroke1->getControlPoint(cpIndex1),
                                           stroke2->getControlPoint(cpIndex2)));
  // connecting the ends in the same shape at the same position
  // means just making the shape self-looped
  if (isSamePos && index1 == index2) {
    stroke1->setSelfLoop();
    return m_strokes[index1];
  }

  std::vector<TThickPoint> points;
  int i, incr = (cpIndex1 == 0) ? -1 : 1;
  int start = ((cpIndex1 == 0) ? cpCount1 - 1 : 0);
  int end   = (isSamePos) ? cpIndex1 : cpIndex1 + incr;
  for (i = start; i != end; i += incr)
    points.push_back(stroke1->getControlPoint(i));
  points.push_back(0.5 * (stroke1->getControlPoint(cpIndex1) +
                          stroke2->getControlPoint(cpIndex2)));
  if (index1 != index2) {
    incr  = (cpIndex2 == 0) ? 1 : -1;
    start = (isSamePos) ? cpIndex2 + incr : cpIndex2;
    end   = ((cpIndex2 == 0) ? cpCount2 - 1 : 0) + incr;
    for (i = start; i != end; i += incr)
      points.push_back(stroke2->getControlPoint(i));
  } else
    points.push_back(stroke2->getControlPoint(cpIndex2));

  TStroke *newStroke = new TStroke(points);
  newStroke->setStyle(styleId);
  newStroke->outlineOptions() = stroke1->outlineOptions();
  // ret = newStroke;
  if (index1 == index2) newStroke->setSelfLoop();
  std::list<TEdge *> oldEdgeList, emptyList;

  computeEdgeList(
      newStroke, m_strokes[index1]->m_edgeList, cpIndex1 == 0,
      (index1 != index2) ? m_strokes[index2]->m_edgeList : emptyList,
      cpIndex2 == 0, oldEdgeList);

  std::vector<int> toBeDeleted;
  toBeDeleted.push_back(index1);
  if (index1 != index2) toBeDeleted.push_back(index2);
  removeStrokes(toBeDeleted, true, false);

  insertStrokeAt(new VIStroke(newStroke, groupId), index1, false);
  computeRegions();
  transferColors(oldEdgeList, m_strokes[index1]->m_edgeList, true, false, true);
  return m_strokes[index1];
}

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

VIStroke *TVectorImage::Imp::joinStrokeSmoothly(int index1, int index2,
                                                int cpIndex1, int cpIndex2) {
  assert(m_strokes[index1]->m_groupId == m_strokes[index2]->m_groupId);

  TGroupId groupId = m_strokes[index1]->m_groupId;

  TStroke *stroke1 = m_strokes[index1]->m_s;
  TStroke *stroke2 = m_strokes[index2]->m_s;
  TStroke *ret;
  int cpCount1 = stroke1->getControlPointCount();
  int cpCount2 = stroke2->getControlPointCount();
  int styleId  = stroke1->getStyle();

  int qCount1 = stroke1->getChunkCount();
  int qCount2 = stroke2->getChunkCount();
  const TThickQuadratic *q1 =
      stroke1->getChunk(cpIndex1 == 0 ? 0 : qCount1 - 1);
  const TThickQuadratic *q2 =
      stroke2->getChunk(cpIndex2 == 0 ? 0 : qCount2 - 1);

  double len1 = q1->getLength();
  assert(len1 >= 0);
  if (len1 <= 0) len1 = 0;
  double w1 = exp(-len1 * 0.01);

  double len2 = q2->getLength();
  assert(len2 >= 0);
  if (len2 <= 0) len2 = 0;
  double w2 = exp(-len2 * 0.01);

  TThickPoint extreme1 = cpIndex1 == 0 ? q1->getThickP0() : q1->getThickP2();
  TThickPoint extreme2 = cpIndex2 == 0 ? q2->getThickP0() : q2->getThickP2();

  TThickPoint m1 = q1->getThickP1();
  TThickPoint m2 = q2->getThickP1();

  TThickPoint p1 = extreme1 * (1 - w1) + m1 * w1;
  TThickPoint p2 = extreme2 * (1 - w2) + m2 * w2;

  TThickPoint middleP = (p1 + p2) * 0.5;

  double angle = fabs(cross(normalize(m1 - middleP), normalize(m2 - middleP)));
  if (angle < 0.05) middleP = (m1 + m2) * 0.5;

  stroke1->setControlPoint(cpIndex1, middleP);
  if (isAlmostZero(len1)) {
    if (cpIndex1 == 0)
      stroke1->setControlPoint(
          1, middleP * 0.1 + stroke1->getControlPoint(2) * 0.9);
    else
      stroke1->setControlPoint(
          cpCount1 - 2,
          middleP * 0.1 + stroke1->getControlPoint(cpCount1 - 3) * 0.9);
  }

  stroke2->setControlPoint(cpIndex2, middleP);
  if (isAlmostZero(len2)) {
    if (cpIndex2 == 0)
      stroke2->setControlPoint(
          1, middleP * 0.1 + stroke2->getControlPoint(2) * 0.9);
    else
      stroke2->setControlPoint(
          cpCount2 - 2,
          middleP * 0.1 + stroke2->getControlPoint(cpCount2 - 3) * 0.9);
  }

  if (stroke1 == stroke2) {
    std::list<TEdge *> oldEdgeList, emptyList;
    computeEdgeList(stroke1, m_strokes[index1]->m_edgeList, cpIndex1 == 0,
                    emptyList, false, oldEdgeList);
    eraseIntersection(index1);
    m_strokes[index1]->m_isNewForFill = true;
    stroke1->setSelfLoop();
    computeRegions();
    transferColors(oldEdgeList, m_strokes[index1]->m_edgeList, true, false,
                   true);
    return m_strokes[index1];
    // nundo->m_newStroke=new TStroke(*stroke1);
    // nundo->m_newStrokeId=stroke1->getId();
  }

  std::vector<TThickPoint> points;
  points.reserve(cpCount1 + cpCount2 - 1);

  int incr = (cpIndex1) ? 1 : -1;
  int stop = cpIndex1;

  int i = cpCount1 - 1 - cpIndex1;
  for (; i != stop; i += incr) points.push_back(stroke1->getControlPoint(i));

  incr = (cpIndex2) ? -1 : 1;
  stop = cpCount2 - 1 - cpIndex2;

  for (i = cpIndex2; i != stop; i += incr)
    points.push_back(stroke2->getControlPoint(i));

  points.push_back(stroke2->getControlPoint(stop));

  TStroke *newStroke = new TStroke(points);
  newStroke->setStyle(styleId);
  newStroke->outlineOptions() = stroke1->outlineOptions();
  ret                         = newStroke;
  // nundo->m_newStroke=new TStroke(*newStroke);
  // nundo->m_newStrokeId=newStroke->getId();
  std::list<TEdge *> oldEdgeList;
  // ofstream os("c:\\temp\\edges.txt");

  // printEdges(os, "****edgelist1", getPalette(),
  // m_imp->m_strokes[index1]->m_edgeList);
  // printEdges(os, "****edgelist2", getPalette(),
  // m_imp->m_strokes[index2]->m_edgeList);

  computeEdgeList(newStroke, m_strokes[index1]->m_edgeList, cpIndex1 == 0,
                  m_strokes[index2]->m_edgeList, cpIndex2 == 0, oldEdgeList);
  // printEdges(os, "****edgelist", getPalette(), oldEdgeList);

  std::vector<int> toBeDeleted;
  toBeDeleted.push_back(index1);
  toBeDeleted.push_back(index2);
  removeStrokes(toBeDeleted, true, false);

  insertStrokeAt(new VIStroke(newStroke, groupId), index1);
  computeRegions();
  transferColors(oldEdgeList, m_strokes[index1]->m_edgeList, true, false, true);

  return m_strokes[index1];

  //  TUndoManager::manager()->add(nundo);
}

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

VIStroke *TVectorImage::joinStroke(int index1, int index2, int cpIndex1,
                                   int cpIndex2, bool isSmooth) {
  int finalStyle = -1;

  if (index1 > index2) {
    finalStyle = getStroke(index1)->getStyle();
    std::swap(index1, index2);
    std::swap(cpIndex1, cpIndex2);
  }
  /*
if (index1==index2) //selfLoop!
{
if (index1>0 && index1<(int)getStrokeCount()-1 &&
  !getStroke(index1-1)->isSelfLoop() &&
  !getStroke(index1+1)->isSelfLoop())
{
for (UINT i = index1+2; i<getStrokeCount() && !getStroke(i)->isSelfLoop(); i++)
  ;
moveStroke(index1, i-1);
index1 = index2 = i-1;
}
}
*/
  VIStroke *ret;
  if (isSmooth)
    ret = m_imp->joinStrokeSmoothly(index1, index2, cpIndex1, cpIndex2);
  else
    ret = m_imp->joinStroke(index1, index2, cpIndex1, cpIndex2);

  if (finalStyle != -1) getStroke(index1)->setStyle(finalStyle);
  return ret;
}

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

VIStroke *TVectorImage::extendStroke(int index, const TThickPoint &p,
                                     int cpIndex, bool isSmooth) {
  if (isSmooth)
    return m_imp->extendStrokeSmoothly(index, p, cpIndex);
  else
    return m_imp->extendStroke(index, p, cpIndex);
}

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

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

TInputStreamInterface &TInputStreamInterface::operator>>(TPixel32 &pixel) {
  return *this >> pixel.r >> pixel.g >> pixel.b >> pixel.m;
}

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

TOutputStreamInterface &TOutputStreamInterface::operator<<(
    const TPixel32 &pixel) {
  return *this << pixel.r << pixel.g << pixel.b << pixel.m;
}

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

void TVectorImage::setAutocloseTolerance(double val) {
  m_imp->m_autocloseTolerance = val;
}

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

double TVectorImage::getAutocloseTolerance() const {
  return m_imp->m_autocloseTolerance;
}

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

TThread::Mutex *TVectorImage::getMutex() const { return m_imp->m_mutex; }

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

void TVectorImage::areaFill(TStroke *stroke, int index, bool m_onlyUnfilled) {
  TVectorImage v;
  v.addStroke(stroke);
  v.findRegions();

  for (UINT i = 0; i < v.getRegionCount(); i++)
    for (UINT j = 0; j < getRegionCount(); j++) {
      if (m_imp->m_insideGroup != TGroupId() &&
          !m_imp->m_insideGroup.isParentOf(
              m_imp->m_strokes[getRegion(j)->getEdge(0)->m_index]->m_groupId))
        continue;

      if (v.getRegion(i)->contains(*getRegion(j)))
        getRegion(j)->setStyle(index);
    }

  v.removeStroke(0);
}

VIStroke *cloneVIStroke(VIStroke *vs) { return new VIStroke(*vs); }

void deleteVIStroke(VIStroke *vs) {
  delete vs;
  vs = 0;
}

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

bool TVectorImage::sameSubGroup(int index0, int index1) const {
  if (index0 < 0 || index1 < 0) return 0;
  return m_imp->m_strokes[index0]->m_groupId.getCommonParentDepth(
             m_imp->m_strokes[index1]->m_groupId) >
         m_imp->m_insideGroup.getDepth();
}

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

int TVectorImage::getCommonGroupDepth(int index0, int index1) const {
  if (index0 < 0 || index1 < 0) return 0;
  return m_imp->m_strokes[index0]->m_groupId.getCommonParentDepth(
      m_imp->m_strokes[index1]->m_groupId);
}

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

int TVectorImage::ungroup(int fromIndex) {
  m_imp->m_insideGroup = TGroupId();

  assert(m_imp->m_strokes[fromIndex]->m_groupId.isGrouped() != 0);
  std::vector<int> changedStrokes;

  int toIndex = fromIndex + 1;

  while (toIndex < (int)m_imp->m_strokes.size() &&
         m_imp->m_strokes[fromIndex]->m_groupId.getCommonParentDepth(
             m_imp->m_strokes[toIndex]->m_groupId) >= 1)
    toIndex++;

  toIndex--;

  TGroupId groupId;

  if (fromIndex > 0 &&
      m_imp->m_strokes[fromIndex - 1]->m_groupId.isGrouped(true) != 0)
    groupId = m_imp->m_strokes[fromIndex - 1]->m_groupId;
  else if (toIndex < (int)m_imp->m_strokes.size() - 1 &&
           m_imp->m_strokes[toIndex + 1]->m_groupId.isGrouped(true) != 0)
    groupId = m_imp->m_strokes[toIndex + 1]->m_groupId;
  else
    groupId = TGroupId(this, true);

  for (int i = fromIndex;
       i <= toIndex || (i < (int)m_imp->m_strokes.size() &&
                        m_imp->m_strokes[i]->m_groupId.isGrouped(true) != 0);
       i++) {
    m_imp->m_strokes[i]->m_groupId.ungroup(groupId);
    changedStrokes.push_back(i);
  }

  notifyChangedStrokes(changedStrokes, std::vector<TStroke *>(), false);

  return toIndex - fromIndex + 1;
}

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

bool TVectorImage::isEnteredGroupStroke(int index) const {
  return m_imp->m_insideGroup.isParentOf(getVIStroke(index)->m_groupId);
}

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

bool TVectorImage::enterGroup(int index) {
  VIStroke *vs = getVIStroke(index);

  if (!vs->m_groupId.isGrouped()) return false;

  int newDepth = vs->m_groupId.getCommonParentDepth(m_imp->m_insideGroup) + 1;

  TGroupId newGroupId = vs->m_groupId;

  while (newGroupId.getDepth() > newDepth) newGroupId = newGroupId.getParent();

  if (newGroupId == m_imp->m_insideGroup) return false;

  m_imp->m_insideGroup = newGroupId;
  return true;
}

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

int TVectorImage::exitGroup() {
  if (m_imp->m_insideGroup == TGroupId()) return -1;

  int i, ret = -1;
  for (i = 0; i < (int)m_imp->m_strokes.size(); i++) {
    if (m_imp->m_strokes[i]->m_groupId.getCommonParentDepth(
            m_imp->m_insideGroup) >= m_imp->m_insideGroup.getDepth()) {
      ret = i;
      break;
    }
  }

  assert(i != m_imp->m_strokes.size());

  m_imp->m_insideGroup = m_imp->m_insideGroup.getParent();
  return ret;
}

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

void TVectorImage::group(int fromIndex, int count) {
  int i;
  assert(count >= 0);
  std::vector<int> changedStroke;

  TGroupId parent = TGroupId(this, false);

  for (i = 0; i < count; i++) {
    m_imp->m_strokes[fromIndex + i]->m_groupId =
        TGroupId(parent, m_imp->m_strokes[fromIndex + i]->m_groupId);
    changedStroke.push_back(fromIndex + i);
  }

  m_imp->rearrangeMultiGroup();  // see method's comment

  m_imp->regroupGhosts(changedStroke);

  notifyChangedStrokes(changedStroke, std::vector<TStroke *>(), false);

#ifdef _DEBUG
  m_imp->checkGroups();
#endif
}

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

int TVectorImage::getGroupDepth(UINT index) const {
  assert(index < m_imp->m_strokes.size());

  return m_imp->m_strokes[index]->m_groupId.isGrouped();
}

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

int TVectorImage::areDifferentGroup(UINT index1, bool isRegion1, UINT index2,
                                    bool isRegion2) const {
  return m_imp->areDifferentGroup(index1, isRegion1, index2, isRegion2);
}

//-------------------------------------------------------------------
/*this method is tricky.
it is not allow to have not-adjacent strokes  of same group.
but it can happen when you group  some already-grouped strokes creating
sub-groups.

example: vi made of 5 strokes, before grouping  (N=no group)
N
N
1
1
N
after grouping became:
2
2
2-1
2-1
2
not allowed!

this method moves strokes, so that adjacent strokes have same group.
so after calling rearrangeMultiGroup the vi became:
2
2
2
2-1
2-1

*/

void TVectorImage::Imp::rearrangeMultiGroup() {
  UINT i, j, k;
  if (m_strokes.size() <= 0) return;
  for (i = 0; i < m_strokes.size() - 1; i++) {
    if (m_strokes[i]->m_groupId.isGrouped() &&
        m_strokes[i + 1]->m_groupId.isGrouped() &&
        m_strokes[i]->m_groupId != m_strokes[i + 1]->m_groupId) {
      TGroupId &prevId   = m_strokes[i]->m_groupId;
      TGroupId &idToMove = m_strokes[i + 1]->m_groupId;
      for (j = i + 1;
           j < m_strokes.size() && m_strokes[j]->m_groupId == idToMove; j++)
        ;
      if (j != m_strokes.size()) {
        j--;  // now range i+1-j contains the strokes to be moved.
        // let's compute where to move them (after last
        for (k = j; k < m_strokes.size() && m_strokes[k]->m_groupId != prevId;
             k++)
          ;
        if (k < m_strokes.size()) {
          for (; k < m_strokes.size() && m_strokes[k]->m_groupId == prevId; k++)
            ;
          moveStrokes(i + 1, j - i, k, false);
          rearrangeMultiGroup();
          return;
        }
      }
    }
  }
}

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

int TVectorImage::Imp::areDifferentGroup(UINT index1, bool isRegion1,
                                         UINT index2, bool isRegion2) const {
  TGroupId group1, group2;

  if (isRegion1) {
    TRegion *r = m_regions[index1];
    for (UINT i = 0; i < r->getEdgeCount(); i++)
      if (r->getEdge(i)->m_index >= 0) {
        group1 = m_strokes[r->getEdge(i)->m_index]->m_groupId;
        break;
      }
  } else
    group1 = m_strokes[index1]->m_groupId;
  if (isRegion2) {
    TRegion *r = m_regions[index2];
    for (UINT i = 0; i < r->getEdgeCount(); i++)
      if (r->getEdge(i)->m_index >= 0) {
        group2 = m_strokes[r->getEdge(i)->m_index]->m_groupId;
        break;
      }
  } else
    group2 = m_strokes[index2]->m_groupId;

  if (!group1 && !group2) return 0;

  if (group1 == group2)
    return -1;
  else
    return group1.getCommonParentDepth(group2);
}

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

int TGroupId::getCommonParentDepth(const TGroupId &id) const {
  int size1 = m_id.size();
  int size2 = id.m_id.size();
  int count;

  for (count = 0; count < std::min(size1, size2); count++)
    if (m_id[size1 - count - 1] != id.m_id[size2 - count - 1]) break;

  return count;
}

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

TGroupId::TGroupId(const TGroupId &parent, const TGroupId &id) {
  assert(parent.m_id[0] > 0);
  assert(id.m_id.size() > 0);

  if (id.isGrouped(true) != 0)
    m_id.push_back(parent.m_id[0]);
  else {
    m_id = id.m_id;
    int i;
    for (i = 0; i < (int)parent.m_id.size(); i++)
      m_id.push_back(parent.m_id[i]);
  }
}

/*
bool TGroupId::sameParent(const TGroupId& id) const
{
assert(!m_id.empty() || !id.m_id.empty());
return m_id.back()==id.m_id.back();
}
*/

TGroupId TGroupId::getParent() const {
  if (m_id.size() <= 1) return TGroupId();

  TGroupId ret = *this;
  ret.m_id.erase(ret.m_id.begin());
  return ret;
}

void TGroupId::ungroup(const TGroupId &id) {
  assert(id.isGrouped(true) != 0);
  assert(!m_id.empty());

  if (m_id.size() == 1)
    m_id[0] = id.m_id[0];
  else
    m_id.pop_back();
}

bool TGroupId::operator==(const TGroupId &id) const {
  if (m_id.size() != id.m_id.size()) return false;
  UINT i;
  for (i = 0; i < m_id.size(); i++)
    if (m_id[i] != id.m_id[i]) return false;

  return true;
}

bool TGroupId::operator<(const TGroupId &id) const {
  assert(!m_id.empty() && !id.m_id.empty());
  int size1 = m_id.size();
  int size2 = id.m_id.size();
  int i;
  for (i = 0; i < std::min(size1, size2); i++)
    if (m_id[size1 - i - 1] != id.m_id[size2 - i - 1])
      return m_id[size1 - i - 1] < id.m_id[size2 - i - 1];

  return size1 < size2;
}

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

int TGroupId::isGrouped(bool implicit) const {
  assert(!m_id.empty());
  assert(m_id[0] != 0);
  if (implicit)
    return (m_id[0] < 0) ? 1 : 0;
  else
    return (m_id[0] > 0) ? m_id.size() : 0;
}

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

TGroupId::TGroupId(TVectorImage *vi, bool isGhost) {
  m_id.push_back((isGhost) ? -(++vi->m_imp->m_maxGhostGroupId)
                           : ++vi->m_imp->m_maxGroupId);
}

#ifdef _DEBUG
void TVectorImage::Imp::checkGroups() {
  TGroupId currGroupId;
  std::set<TGroupId> groupSet;
  std::set<TGroupId>::iterator it;
  UINT i = 0;

  while (i < m_strokes.size()) {
    // assert(m_strokes[i]->m_groupId!=currGroupId);
    // assert(i==0 ||
    // m_strokes[i-1]->m_groupId.isGrouped()!=m_strokes[i]->m_groupId.isGrouped()!=0
    // ||
    //       (m_strokes[i]->m_groupId.isGrouped()!=0 &&
    //       m_strokes[i-1]->m_groupId!=m_strokes[i]->m_groupId));

    currGroupId = m_strokes[i]->m_groupId;
    it          = groupSet.find(currGroupId);
    if (it != groupSet.end())  // esisteva gia un gruppo con questo id!
      assert(!"errore: due gruppi con lo stesso id!");
    else
      groupSet.insert(currGroupId);

    while (i < m_strokes.size() && m_strokes[i]->m_groupId == currGroupId) i++;
  }
}
#endif

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

bool TVectorImage::canMoveStrokes(int strokeIndex, int count,
                                  int moveBefore) const {
  return m_imp->canMoveStrokes(strokeIndex, count, moveBefore);
}

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

// verifica se si possono spostare le stroke  da strokeindex a
// strokeindex+count-1 prima della posizione moveBefore;
// per fare questo fa un vettore in cui mette tutti i gruppi nella  posizione
// dopo lo
// spostamento e verifica che sia un configurazione di gruppi ammessa.

bool TVectorImage::Imp::canMoveStrokes(int strokeIndex, int count,
                                       int moveBefore) const {
  if (m_maxGroupId <= 1)  // non ci sono gruppi!
    return true;

  int i, j = 0;

  std::vector<TGroupId> groupsAfterMoving(m_strokes.size());
  if (strokeIndex < moveBefore) {
    for (i = 0; i < strokeIndex; i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;

    for (i = strokeIndex + count; i < moveBefore; i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;

    for (i = strokeIndex; i < strokeIndex + count; i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;

    for (i = moveBefore; i < (int)m_strokes.size(); i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;
  } else {
    for (i = 0; i < moveBefore; i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;

    for (i = strokeIndex; i < strokeIndex + count; i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;

    for (i = moveBefore; i < strokeIndex; i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;

    for (i = strokeIndex + count; i < (int)m_strokes.size(); i++)
      groupsAfterMoving[j++] = m_strokes[i]->m_groupId;
  }

  assert(j == (int)m_strokes.size());

  i = 0;
  TGroupId currGroupId;
  std::set<TGroupId> groupSet;

  while (i < (int)groupsAfterMoving.size()) {
    currGroupId = groupsAfterMoving[i];
    if (groupSet.find(currGroupId) !=
        groupSet.end())  // esisteva gia un gruppo con questo id!
    {
      if (!currGroupId.isGrouped(true))  // i gruppi impliciti non contano
        return false;
    } else
      groupSet.insert(currGroupId);

    while (i < (int)groupsAfterMoving.size() &&
           groupsAfterMoving[i] == currGroupId)
      i++;
  }

  return true;
}

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

void TVectorImage::Imp::regroupGhosts(std::vector<int> &changedStrokes) {
  TGroupId currGroupId;
  std::set<TGroupId> groupMap;
  std::set<TGroupId>::iterator it;
  UINT i = 0;

  while (i < m_strokes.size()) {
    assert(m_strokes[i]->m_groupId != currGroupId);
    assert(i == 0 ||
           m_strokes[i - 1]->m_groupId.isGrouped() !=
               m_strokes[i]->m_groupId.isGrouped() != 0 ||
           (m_strokes[i]->m_groupId.isGrouped() != 0 &&
            m_strokes[i - 1]->m_groupId != m_strokes[i]->m_groupId));

    currGroupId = m_strokes[i]->m_groupId;
    it          = groupMap.find(currGroupId);
    if (it != groupMap.end())  // esisteva gia un gruppo con questo id!
    {
      if (currGroupId.isGrouped() != 0)
        assert(!"errore: due gruppi con lo stesso id!");
      else  // gruppo ghost; gli do un nuovo id
      {
        TGroupId newGroup(m_vi, true);

        while (i < m_strokes.size() &&
               m_strokes[i]->m_groupId.isGrouped(true) != 0) {
          m_strokes[i]->m_groupId = newGroup;
          changedStrokes.push_back(i);
          i++;
        }
      }
    } else {
      groupMap.insert(currGroupId);
      while (i < m_strokes.size() &&
             ((currGroupId.isGrouped(false) != 0 &&
               m_strokes[i]->m_groupId == currGroupId) ||
              (currGroupId.isGrouped(true) != 0 &&
               m_strokes[i]->m_groupId.isGrouped(true) != 0))) {
        if (m_strokes[i]->m_groupId != currGroupId) {
          m_strokes[i]->m_groupId = currGroupId;
          changedStrokes.push_back(i);
        }
        i++;
      }
    }
  }
}

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

bool TVectorImage::canEnterGroup(int strokeIndex) const {
  VIStroke *vs = m_imp->m_strokes[strokeIndex];

  if (!vs->m_groupId.isGrouped()) return false;

  return m_imp->m_insideGroup == TGroupId() ||
         vs->m_groupId != m_imp->m_insideGroup;
}

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

bool TVectorImage::inCurrentGroup(int strokeIndex) const {
  return m_imp->inCurrentGroup(strokeIndex);
}

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

bool TVectorImage::Imp::inCurrentGroup(int strokeIndex) const {
  return m_insideGroup == TGroupId() ||
         m_insideGroup.isParentOf(m_strokes[strokeIndex]->m_groupId);
}

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

bool TVectorImage::selectable(int strokeIndex) const {
  return (m_imp->m_insideGroup != m_imp->m_strokes[strokeIndex]->m_groupId &&
          inCurrentGroup(strokeIndex));
}

//--------------------------------------------------------------------------------------------------
namespace {

bool containsNoSubregion(const TRegion *r, const TPointD &p) {
  if (r->contains(p)) {
    for (unsigned int i = 0; i < r->getSubregionCount(); i++)
      if (r->getSubregion(i)->contains(p)) return false;
    return true;
  } else
    return false;
}
};  // namespace

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

int TVectorImage::getGroupByStroke(UINT index) const {
  VIStroke *viStroke = getVIStroke(index);
  return viStroke->m_groupId.m_id.back();
}

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

int TVectorImage::getGroupByRegion(UINT index) const {
  TRegion *r = m_imp->m_regions[index];
  for (UINT i = 0; i < r->getEdgeCount(); i++)
    if (r->getEdge(i)->m_index >= 0) {
      return m_imp->m_strokes[r->getEdge(i)->m_index]->m_groupId.m_id.back();
    }

  return -1;
}

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

int TVectorImage::pickGroup(const TPointD &pos, bool onEnteredGroup) const {
  if (onEnteredGroup && isInsideGroup() == 0) return -1;

  // double maxDist2 = 50*tglGetPixelSize2();

  int strokeIndex = getStrokeCount() - 1;

  while (strokeIndex >=
         0)  // ogni ciclo di while esplora un gruppo; ciclo sugli stroke
  {
    if (!isStrokeGrouped(strokeIndex)) {
      strokeIndex--;
      continue;
    }

    bool entered = isInsideGroup() > 0 && isEnteredGroupStroke(strokeIndex);

    if ((onEnteredGroup || entered) && (!onEnteredGroup || !entered)) {
      strokeIndex--;
      continue;
    }

    int currStrokeIndex = strokeIndex;

    while (strokeIndex >= 0 &&
           getCommonGroupDepth(strokeIndex, currStrokeIndex) > 0) {
      TStroke *s = getStroke(strokeIndex);
      double outT;
      int chunkIndex;
      double dist2;
      bool ret = s->getNearestChunk(pos, outT, chunkIndex, dist2);
      if (ret) {
        TThickPoint p = s->getChunk(chunkIndex)->getThickPoint(outT);
        if (p.thick < 0.1) p.thick = 1;
        if (sqrt(dist2) <= 1.5 * p.thick) return strokeIndex;
      }

      /*TThickPoint p = s->getThickPoint(s->getW(pos));

double dist = tdistance( TThickPoint(pos,0), p);
if (dist<1.2*p.thick/2.0)
return strokeIndex;*/
      strokeIndex--;
    }
  }

  strokeIndex = getStrokeCount() - 1;
  int ret     = -1;

  while (strokeIndex >=
         0)  // ogni ciclo di while esplora un gruppo; ciclo sulle regions
  {
    if (!isStrokeGrouped(strokeIndex)) {
      strokeIndex--;
      continue;
    }

    bool entered = isInsideGroup() > 0 && isEnteredGroupStroke(strokeIndex);

    if ((onEnteredGroup || entered) && (!onEnteredGroup || !entered)) {
      strokeIndex--;
      continue;
    }

    TRegion *currR = 0;
    for (UINT regionIndex = 0; regionIndex < getRegionCount(); regionIndex++) {
      TRegion *r = getRegion(regionIndex);

      int i, regionStrokeIndex = -1;
      for (i = 0; i < (int)r->getEdgeCount() && regionStrokeIndex < 0; i++)
        regionStrokeIndex = r->getEdge(i)->m_index;

      if (regionStrokeIndex >= 0 &&
          sameSubGroup(regionStrokeIndex, strokeIndex) &&
          containsNoSubregion(r, pos)) {
        if (!currR || currR->contains(*r)) {
          currR = r;
          ret   = regionStrokeIndex;
        }
      }
    }
    if (currR != 0) {
      assert(m_palette);
      const TSolidColorStyle *st = dynamic_cast<const TSolidColorStyle *>(
          m_palette->getStyle(currR->getStyle()));
      if (!st || st->getMainColor() != TPixel::Transparent) return ret;
    }

    while (strokeIndex > 0 &&
           getCommonGroupDepth(strokeIndex, strokeIndex - 1) > 0)
      strokeIndex--;
    strokeIndex--;
  }

  return -1;
}

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

int TVectorImage::pickGroup(const TPointD &pos) const {
  int index;
  if ((index = pickGroup(pos, true)) == -1) return pickGroup(pos, false);

  return index;
}

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