

// TnzTools includes
#include "tools/toolhandle.h"
#include "tools/toolutils.h"
#include "tools/tool.h"
#include "tools/cursors.h"

// TnzQt includes
#include "toonzqt/imageutils.h"

// TnzLib includes
#include "toonz/txshlevelhandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/strokegenerator.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/stage2.h"
#include "toonz/preferences.h"

// TnzBase includes
#include "tenv.h"

// TnzCore includes
#include "tmathutil.h"
#include "tundo.h"
#include "tstroke.h"
#include "tvectorimage.h"
#include "ttoonzimage.h"
#include "tproperty.h"
#include "tgl.h"
#include "tinbetween.h"
#include "drawutil.h"

// Qt includes
#include <QCoreApplication>  // For Qt translation support

using namespace ToolUtils;

TEnv::DoubleVar EraseVectorSize("InknpaintEraseVectorSize", 10);
TEnv::StringVar EraseVectorType("InknpaintEraseVectorType", "Normal");
TEnv::StringVar EraseVectorInterpolation("InknpaintEraseVectorInterpolation",
                                         "Linear");
TEnv::IntVar EraseVectorSelective("InknpaintEraseVectorSelective", 0);
TEnv::IntVar EraseVectorInvert("InknpaintEraseVectorInvert", 0);
TEnv::IntVar EraseVectorRange("InknpaintEraseVectorRange", 0);

//=============================================================================
// Eraser Tool
//=============================================================================

namespace {

#define NORMAL_ERASE L"Normal"
#define RECT_ERASE L"Rectangular"
#define FREEHAND_ERASE L"Freehand"
#define POLYLINE_ERASE L"Polyline"
#define SEGMENT_ERASE L"Segment"

#define LINEAR_INTERPOLATION L"Linear"
#define EASE_IN_INTERPOLATION L"Ease In"
#define EASE_OUT_INTERPOLATION L"Ease Out"
#define EASE_IN_OUT_INTERPOLATION L"Ease In/Out"

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

const double minDistance2 = 16.0;  // 4 pixels

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

void mapToVector(const std::map<int, VIStroke *> &theMap,
                 std::vector<int> &theVect) {
  assert(theMap.size() == theVect.size());
  std::map<int, VIStroke *>::const_iterator it = theMap.begin();
  UINT i                                       = 0;
  for (; it != theMap.end(); ++it) {
    theVect[i++] = it->first;
  }
}

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

class UndoEraser final : public ToolUtils::TToolUndo {
  std::vector<TFilledRegionInf> m_oldFillInformation, m_newFillInformation;

  int m_row;
  int m_column;

public:
  std::map<int, VIStroke *> m_originalStrokes;
  std::map<int, VIStroke *> m_newStrokes;

  UndoEraser(TXshSimpleLevel *level, const TFrameId &frameId)
      : ToolUtils::TToolUndo(level, frameId) {
    TVectorImageP image = level->getFrame(m_frameId, true);
    if (!image) return;
    TTool::Application *app = TTool::getApplication();
    if (app) {
      m_row    = app->getCurrentFrame()->getFrame();
      m_column = app->getCurrentColumn()->getColumnIndex();
    }
    ImageUtils::getFillingInformationInArea(image, m_oldFillInformation,
                                            image->getBBox());
  }

  void onAdd() override {
    TVectorImageP image = m_level->getFrame(m_frameId, true);
    assert(!!image);
    if (!image) return;
    ImageUtils::getFillingInformationInArea(image, m_newFillInformation,
                                            image->getBBox());
  }

  ~UndoEraser() {
    std::map<int, VIStroke *>::const_iterator it;
    for (it = m_originalStrokes.begin(); it != m_originalStrokes.end(); ++it)
      deleteVIStroke(it->second);
    for (it = m_newStrokes.begin(); it != m_newStrokes.end(); ++it)
      deleteVIStroke(it->second);
  }

  void addOldStroke(int index, VIStroke *stroke) {
    VIStroke *s = cloneVIStroke(stroke);
    m_originalStrokes.insert(std::map<int, VIStroke *>::value_type(index, s));
  }

  void addNewStroke(int index, VIStroke *stroke) {
    VIStroke *s = cloneVIStroke(stroke);
    m_newStrokes.insert(std::map<int, VIStroke *>::value_type(index, s));
  }

  void undo() const override {
    TTool::Application *app = TTool::getApplication();
    if (!app) return;

    TFrameId currentFid;
    if (app->getCurrentFrame()->isEditingScene()) {
      app->getCurrentColumn()->setColumnIndex(m_column);
      app->getCurrentFrame()->setFrame(m_row);
      currentFid = TFrameId(m_row + 1);
    } else {
      app->getCurrentFrame()->setFid(m_frameId);
      currentFid = m_frameId;
    }

    TVectorImageP image = m_level->getFrame(m_frameId, true);
    assert(image);
    if (!image) return;
    QMutexLocker lock(image->getMutex());
    std::vector<int> newStrokeIndex(m_newStrokes.size());
    mapToVector(m_newStrokes, newStrokeIndex);

    image->removeStrokes(newStrokeIndex, true, false);

    std::map<int, VIStroke *>::const_iterator it = m_originalStrokes.begin();
    UINT i                                       = 0;
    VIStroke *s;
    for (; it != m_originalStrokes.end(); ++it) {
      s = cloneVIStroke(it->second);
      image->insertStrokeAt(s, it->first);
    }

    if (image->isComputedRegionAlmostOnce())
      image->findRegions();  // TODO: Remove in the future. Needed because
                             // removeStrokes is faster when not calculating
                             // regions, but then everything crashes

    UINT size = m_oldFillInformation.size();
    if (!size) {
      app->getCurrentXsheet()->notifyXsheetChanged();
      notifyImageChanged();
      return;
    }

    TRegion *reg;
    i = 0;
    for (; i < size; i++) {
      reg = image->getRegion(m_oldFillInformation[i].m_regionId);
      assert(reg);
      if (reg) reg->setStyle(m_oldFillInformation[i].m_styleId);
    }
    app->getCurrentXsheet()->notifyXsheetChanged();
    notifyImageChanged();
  }

  void redo() const override {
    TTool::Application *app = TTool::getApplication();
    if (!app) return;

    TFrameId currentFid;
    if (app->getCurrentFrame()->isEditingScene()) {
      app->getCurrentColumn()->setColumnIndex(m_column);
      app->getCurrentFrame()->setFrame(m_row);
      currentFid = TFrameId(m_row + 1);
    } else {
      app->getCurrentFrame()->setFid(m_frameId);
      currentFid = m_frameId;
    }
    TVectorImageP image = m_level->getFrame(m_frameId, true);
    assert(image);
    if (!image) return;

    QMutexLocker lock(image->getMutex());
    std::vector<int> oldStrokeIndex(m_originalStrokes.size());
    mapToVector(m_originalStrokes, oldStrokeIndex);

    image->removeStrokes(oldStrokeIndex, true, false);

    std::map<int, VIStroke *>::const_iterator it = m_newStrokes.begin();
    UINT i                                       = 0;
    VIStroke *s;
    for (; it != m_newStrokes.end(); ++it) {
      s = cloneVIStroke(it->second);
      image->insertStrokeAt(s, it->first);
    }

    if (image->isComputedRegionAlmostOnce())
      image->findRegions();  // TODO: Remove in the future. Needed because
                             // removeStrokes is faster when not calculating
                             // regions, but then everything crashes

    UINT size = m_newFillInformation.size();
    if (!size) {
      app->getCurrentXsheet()->notifyXsheetChanged();
      notifyImageChanged();
      return;
    }

    TRegion *reg;
    i = 0;
    for (; i < size; i++) {
      reg = image->getRegion(m_newFillInformation[i].m_regionId);
      assert(reg);
      if (reg) reg->setStyle(m_newFillInformation[i].m_styleId);
    }
    app->getCurrentXsheet()->notifyXsheetChanged();
    notifyImageChanged();
  }

  int getSize() const override {
    return sizeof(*this) +
           (m_oldFillInformation.capacity() + m_newFillInformation.capacity()) *
               sizeof(TFilledRegionInf) +
           500;
  }

  QString getToolName() override { return QString("Vector Eraser Tool"); }

  int getHistoryType() override { return HistoryType::EraserTool; }
};

}  // namespace

//=============================================================================
//  EraserTool class declaration
//-----------------------------------------------------------------------------

class EraserTool final : public TTool {
  Q_DECLARE_TR_FUNCTIONS(EraserTool)

public:
  EraserTool();
  ~EraserTool();

  ToolType getToolType() const override { return TTool::LevelWriteTool; }

  void draw() override;

  void startErase(TVectorImageP vi, const TPointD &pos);
  void erase(TVectorImageP vi, const TPointD &pos);
  void erase(TVectorImageP vi, TRectD &rect);

  void stopErase(TVectorImageP vi);

  void leftButtonDown(const TPointD &pos, const TMouseEvent &e) override;
  void leftButtonDrag(const TPointD &pos, const TMouseEvent &e) override;
  void leftButtonUp(const TPointD &pos, const TMouseEvent &) override;
  void leftButtonDoubleClick(const TPointD &pos, const TMouseEvent &e) override;
  void mouseMove(const TPointD &pos, const TMouseEvent &e) override;
  bool onPropertyChanged(std::string propertyName) override;
  void onEnter() override;
  void onLeave() override;
  void onActivate() override;

  TPropertyGroup *getProperties(int targetType) override { return &m_prop; }

  int getCursorId() const override { return ToolCursor::EraserCursor; }
  void onImageChanged() override;

  /*-- When the tool is switched during dragging, perform erase termination --*/
  void onDeactivate() override;

private:
  typedef void (EraserTool::*EraseFunction)(const TVectorImageP vi,
                                            TStroke *stroke);

  TPropertyGroup m_prop;

  TEnumProperty m_eraseType;
  TEnumProperty m_interpolation;
  TDoubleProperty m_toolSize;
  TBoolProperty m_selective;
  TBoolProperty m_invertOption;
  TBoolProperty m_multi;

  double m_pointSize, m_distance2;

  TPointD m_mousePos,  //!< Current mouse position.
      m_oldMousePos,   //!< Previous mouse position.
      m_brushPos,      //!< Position where the brush will be painted.
      m_firstPos;      //!< First point inserted either in m_track or m_polyline
                       //! (depending on selected erase mode).
  UndoEraser *m_undo;
  std::vector<int> m_indexes;

  TRectD m_selectingRect, m_firstRect;

  TFrameId m_firstFrameId, m_veryFirstFrameId;

  TXshSimpleLevelP m_level;
  std::pair<int, int> m_currCell;

  StrokeGenerator m_track;  //!< Freehand selection generator.

  std::vector<TPointD> m_polyline;  //!< Polyline points.

  TStroke *m_stroke;  //!< Stores the stroke generated by m_track.
  TStroke
      *m_firstStroke;  //!< Stores the first stroke in the "frame range" case.
  TImageP m_activeImage = nullptr;  // needed if a frame is changed mid-erase
  double m_thick;

  bool m_firstTime, m_active, m_firstFrameSelected;

private:
  void resetMulti();

  void updateTranslation() override;

  // Methods to draw the line in Freehand mode
  void startFreehand(const TPointD &pos);
  void freehandDrag(const TPointD &pos);
  void closeFreehand(const TPointD &pos);

  // Methods to draw the line in Polyline mode
  void addPointPolyline(const TPointD &pos);
  void closePolyline(const TPointD &pos);

  void eraseRegion(const TVectorImageP vi, TStroke *stroke);

  void eraseSegments(const TVectorImageP vi, TStroke *eraseStroke);

  void multiEraseRect(TFrameId firstFrameId, TFrameId lastFrameId,
                      TRectD firstRect, TRectD lastRect, bool invert);
  void doMultiErase(TFrameId &firstFrameId, TFrameId &lastFrameId,
                    const TStroke *firstStroke, const TStroke *lastStroke,
                    EraseFunction eraseFunction);
  void doErase(double t, const TXshSimpleLevelP &sl, const TFrameId &fid,
               const TVectorImageP &firstImage, const TVectorImageP &lastImage,
               EraseFunction eraseFunction);
  void multiErase(TStroke *stroke, const TMouseEvent &e,
                  EraseFunction eraseFunction);

} eraserTool;

//=============================================================================
//  EraserTool implementation
//-----------------------------------------------------------------------------

EraserTool::EraserTool()
    : TTool("T_Eraser")
    , m_eraseType("Type:")  // "W_ToolOptions_Erasetype"
    , m_interpolation("interpolation:")
    , m_toolSize("Size:", 1, 1000, 10)  // "W_ToolOptions_EraserToolSize"
    , m_selective("Selective", false)   // "W_ToolOptions_Selective"
    , m_invertOption("Invert", false)   // "W_ToolOptions_Invert"
    , m_multi("Frame Range", false)     // "W_ToolOptions_FrameRange"
    , m_pointSize(-1)
    , m_undo(nullptr)
    , m_currCell(-1, -1)
    , m_stroke(nullptr)
    , m_firstStroke(nullptr)
    , m_thick(5)
    , m_active(false)
    , m_firstTime(true) {
  bind(TTool::VectorImage);

  m_toolSize.setNonLinearSlider();

  m_prop.bind(m_toolSize);
  m_prop.bind(m_eraseType);
  m_eraseType.addValue(NORMAL_ERASE);
  m_eraseType.addValue(RECT_ERASE);
  m_eraseType.addValue(FREEHAND_ERASE);
  m_eraseType.addValue(POLYLINE_ERASE);
  m_eraseType.addValue(SEGMENT_ERASE);
  m_prop.bind(m_selective);
  m_prop.bind(m_invertOption);
  m_prop.bind(m_multi);
  m_prop.bind(m_interpolation);
  m_interpolation.addValue(LINEAR_INTERPOLATION);
  m_interpolation.addValue(EASE_IN_INTERPOLATION);
  m_interpolation.addValue(EASE_OUT_INTERPOLATION);
  m_interpolation.addValue(EASE_IN_OUT_INTERPOLATION);

  m_selective.setId("Selective");
  m_invertOption.setId("Invert");
  m_multi.setId("FrameRange");
  m_eraseType.setId("Type");
  m_interpolation.setId("Interpolation");
}

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

EraserTool::~EraserTool() {
  // Release allocated memory
  if (m_stroke) {
    delete m_stroke;
    m_stroke = nullptr;
  }

  if (m_firstStroke) {
    delete m_firstStroke;
    m_firstStroke = nullptr;
  }

  if (m_undo) {
    delete m_undo;
    m_undo = nullptr;
  }
}

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

void EraserTool::updateTranslation() {
  m_toolSize.setQStringName(tr("Size:"));
  m_selective.setQStringName(tr("Selective"));
  m_invertOption.setQStringName(tr("Invert"));
  m_multi.setQStringName(tr("Frame Range"));
  m_eraseType.setQStringName(tr("Type:"));
  m_eraseType.setItemUIName(NORMAL_ERASE, tr("Normal"));
  m_eraseType.setItemUIName(RECT_ERASE, tr("Rectangular"));
  m_eraseType.setItemUIName(FREEHAND_ERASE, tr("Freehand"));
  m_eraseType.setItemUIName(POLYLINE_ERASE, tr("Polyline"));
  m_eraseType.setItemUIName(SEGMENT_ERASE, tr("Segment"));

  m_interpolation.setQStringName(tr(""));
  m_interpolation.setItemUIName(LINEAR_INTERPOLATION, tr("Linear"));
  m_interpolation.setItemUIName(EASE_IN_INTERPOLATION, tr("Ease In"));
  m_interpolation.setItemUIName(EASE_OUT_INTERPOLATION, tr("Ease Out"));
  m_interpolation.setItemUIName(EASE_IN_OUT_INTERPOLATION, tr("Ease In/Out"));
}

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

void EraserTool::draw() {
  if (m_pointSize <= 0) return;

  double pixelSize2 = getPixelSize() * getPixelSize();
  m_thick           = pixelSize2 / 2.0;

  TImageP image(getImage(false));
  TVectorImageP vi = image;
  if (vi) {
    bool blackBg = ToonzCheck::instance()->getChecks() & ToonzCheck::eBlackBg;
    if (m_eraseType.getValue() == RECT_ERASE) {
      TPixel color = blackBg ? TPixel32::White : TPixel32::Red;
      if (m_multi.getValue() && m_firstFrameSelected)
        drawRect(m_firstRect, color, 0x3F33, true);

      if (m_active || (m_multi.getValue() && !m_firstFrameSelected))
        drawRect(m_selectingRect, color, 0xFFFF, true);
    }
    if (m_eraseType.getValue() == NORMAL_ERASE) {
      // If toggled off, don't draw brush outline
      if (!Preferences::instance()->isCursorOutlineEnabled()) return;

      tglColor(TPixel32(255, 0, 255));
      tglDrawCircle(m_brushPos, m_pointSize);
    }
    if ((m_eraseType.getValue() == FREEHAND_ERASE ||
         m_eraseType.getValue() == POLYLINE_ERASE ||
         m_eraseType.getValue() == SEGMENT_ERASE) &&
        m_multi.getValue() && m_firstStroke) {
      TPixel color = blackBg ? TPixel32::White : TPixel32::Red;
      tglColor(color);
      glPushAttrib(GL_ALL_ATTRIB_BITS);
      glEnable(GL_BLEND);
      glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
      if (m_firstFrameSelected) {
        glLineStipple(1, 0x3F33);
        glEnable(GL_LINE_STIPPLE);
      }
      drawStrokeCenterline(*m_firstStroke, 1);
      glPopAttrib();
    }
    if (m_eraseType.getValue() == POLYLINE_ERASE && !m_polyline.empty()) {
      TPixel color = blackBg ? TPixel32::White : TPixel32::Black;
      tglColor(color);
      tglDrawCircle(m_polyline[0], 2);
      glBegin(GL_LINE_STRIP);
      for (UINT i = 0; i < m_polyline.size(); i++) tglVertex(m_polyline[i]);
      tglVertex(m_mousePos);
      glEnd();
    } else if ((m_eraseType.getValue() == FREEHAND_ERASE ||
                m_eraseType.getValue() == SEGMENT_ERASE) &&
               !m_track.isEmpty()) {
      TPixel color = blackBg ? TPixel32::White : TPixel32::Black;
      tglColor(color);
      glPushMatrix();
      m_track.drawAllFragments();
      glPopMatrix();
    }
  }
}

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

void EraserTool::resetMulti() {
  m_firstFrameSelected = false;
  m_firstRect.empty();

  m_selectingRect.empty();
  TTool::Application *application = TTool::getApplication();
  if (!application) return;

  m_firstFrameId = m_veryFirstFrameId = getCurrentFid();
  m_level = application->getCurrentLevel()->getLevel()
                ? application->getCurrentLevel()->getLevel()->getSimpleLevel()
                : nullptr;

  if (m_firstStroke) {
    delete m_firstStroke;
    m_firstStroke = nullptr;
  }
}

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

void EraserTool::startErase(TVectorImageP vi, const TPointD &pos) {
  UINT size = vi->getStrokeCount();
  m_indexes.resize(size);
  for (UINT i = 0; i < size; i++) m_indexes[i] = i;

  if (m_undo) {
    delete m_undo;
    m_undo = nullptr;
  }
  TXshSimpleLevel *level =
      TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
  m_undo        = new UndoEraser(level, getCurrentFid());
  m_oldMousePos = pos;
  m_distance2   = minDistance2 * getPixelSize() * getPixelSize();
  erase(vi, pos);
}

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

void EraserTool::erase(TVectorImageP vi, const TPointD &pos) {
  std::vector<int>::iterator it = m_indexes.begin();
  m_distance2 += tdistance2(m_oldMousePos, pos);

  if (m_distance2 < minDistance2 * getPixelSize() * getPixelSize()) return;

  m_distance2   = 0;
  m_oldMousePos = pos;

  // Square circumscribing the circle
  TRectD circumscribedSquare(pos.x - m_pointSize, pos.y - m_pointSize,
                             pos.x + m_pointSize, pos.y + m_pointSize);
  if (!circumscribedSquare.overlaps(vi->getBBox())) {
    invalidate();
    return;
  }

  std::vector<double> intersections;
  std::vector<DoublePair> sortedWRanges;

  std::vector<TStroke *> splitStrokes;
  double rectEdge_2 = m_pointSize * M_SQRT1_2;

  // Square inscribed in the circle
  TRectD inscribedSquare(pos.x - rectEdge_2, pos.y - rectEdge_2,
                         pos.x + rectEdge_2, pos.y + rectEdge_2);

  UINT i            = 0;
  double pointSize2 = sq(m_pointSize);

  std::vector<int> oneStrokeIndex(1);
  int index = TTool::getApplication()->getCurrentLevelStyleIndex();
  QMutexLocker lock(vi->getMutex());
  while (i < vi->getStrokeCount()) {
    assert(it != m_indexes.end());

    TStroke *oldStroke = vi->getStroke(i);
    if (!vi->inCurrentGroup(i) ||
        (m_selective.getValue() && oldStroke->getStyle() != index)) {
      i++;
      it++;
      continue;
    }

    TRectD strokeBBox = oldStroke->getBBox();

    if (!circumscribedSquare.overlaps(
            strokeBBox)) {  // Stroke outside the square circumscribing the
                            // circle
      i++;
      it++;
      continue;
    }

    if (inscribedSquare.contains(
            strokeBBox)) {  // Stroke entirely contained within the square
                            // inscribed in the circle
      if (*it != -1) m_undo->addOldStroke(*it, vi->getVIStroke(i));
      oneStrokeIndex[0] = i;
      vi->removeStrokes(oneStrokeIndex, true, true);

      it = m_indexes.erase(it);
      continue;
    }

    splitStrokes.clear();
    intersections.clear();
    /*int intersNum=*/intersect(*oldStroke, pos, m_pointSize, intersections);

    /*
#ifdef _DEBUG
if(intersections.size()==2 && intersections[0]==intersections[1])
{
intersections.clear();
intersect( *oldStroke, pos, m_pointSize, intersections );
}
#endif
*/

    if (intersections.empty()) {
      // WE SHOULD ONLY CHECK ONE POINT BECAUSE IF THE STROKE DOES NOT
      // INTERSECT THE CIRCLE AND CONTAINS AT LEAST ONE POINT, IT CONTAINS ALL.
      // BUT SINCE I DON'T TRUST THE INTERSECT, I CHECK A COUPLE AND FAVOR (WITH
      // AND) NON-CONTAINMENT, AS IT'S BETTER TO DELETE ONE LESS THING THAN ONE
      // MORE
      if (tdistance2(oldStroke->getPoint(0), pos) < pointSize2 &&
          tdistance2(oldStroke->getPoint(1), pos) <
              pointSize2) {  // Stroke entirely contained within the circle
        if (*it != -1) m_undo->addOldStroke(*it, vi->getVIStroke(i));
        oneStrokeIndex[0] = i;
        vi->removeStrokes(oneStrokeIndex, true, true);
        it = m_indexes.erase(it);
      } else {  // Not hit
        i++;
        it++;
      }
      continue;
    }

    // At least one intersection

    if (intersections.size() == 1) {
      if (oldStroke->isSelfLoop()) {  // Only one intersection of a closed
                                      // stroke with a circle should only happen
        // in case of grazing, so pretend nothing happened
        i++;
        it++;
        continue;
      }

      if (*it != -1) m_undo->addOldStroke(*it, vi->getVIStroke(i));

      double w0            = intersections[0];
      TThickPoint hitPoint = oldStroke->getThickPoint(w0);
      int chunck;
      double t;
      oldStroke->getChunkAndT(w0, chunck, t);

      int chunckIndex;
      double w1;
      if (tdistance2(oldStroke->getPoint(0), pos) < pointSize2) {
        chunckIndex = oldStroke->getChunkCount() - 1;
        w1          = 1;
      } else {
        chunckIndex = 0;
        w1          = 0;
      }

      UINT cI;
      std::vector<TThickPoint> points;
      if (w1 == 0) {
        for (cI = chunckIndex; cI < (UINT)chunck; cI++) {
          points.push_back(oldStroke->getChunk(cI)->getThickP0());
          points.push_back(oldStroke->getChunk(cI)->getThickP1());
        }

        TThickQuadratic t1, t2;
        oldStroke->getChunk(chunck)->split(t, t1, t2);
        points.push_back(t1.getThickP0());
        points.push_back(t1.getThickP1());
        points.push_back(hitPoint);
      } else {
        TThickQuadratic t1, t2;
        oldStroke->getChunk(chunck)->split(t, t1, t2);
        points.push_back(hitPoint);
        points.push_back(t2.getThickP1());
        points.push_back(t2.getThickP2());

        for (cI = chunck + 1; cI <= (UINT)chunckIndex; cI++) {
          points.push_back(oldStroke->getChunk(cI)->getThickP1());
          points.push_back(oldStroke->getChunk(cI)->getThickP2());
        }
      }

      oldStroke->reshape(&(points[0]), points.size());

      vi->notifyChangedStrokes(i);  // For now like this, but it's slow

      *it = -1;
      i++;
      it++;
      continue;
    }

    // Multiple intersections

    if (intersections.size() & 1 &&
        oldStroke->isSelfLoop()) {  // Should never happen
      assert(0);
      i++;
      it++;
      continue;
    }

    if (intersections.size() == 2 &&
        intersections[0] == intersections[1]) {  // Only grazed
      i++;
      it++;
      continue;
    }

    UINT oldStrokeSize = vi->getStrokeCount();

    if (*it != -1) m_undo->addOldStroke(*it, vi->getVIStroke(i));

    sortedWRanges.clear();

    if (tdistance2(vi->getStroke(i)->getPoint(0), pos) > pointSize2) {
      if (intersections[0] == 0.0)
        intersections.erase(intersections.begin());
      else
        intersections.insert(intersections.begin(), 0.0);
    }

    if (intersections[0] != 1.0) intersections.push_back(1.0);

    sortedWRanges.reserve(intersections.size() / 2);

    for (UINT j = 0; j < intersections.size() - 1; j += 2)
      sortedWRanges.push_back(
          std::make_pair(intersections[j], intersections[j + 1]));

#ifdef _DEBUG

    for (UINT kkk = 0; kkk < sortedWRanges.size() - 1; kkk++) {
      assert(sortedWRanges[kkk].first < sortedWRanges[kkk].second);
      assert(sortedWRanges[kkk].second <= sortedWRanges[kkk + 1].first);
    }
    assert(sortedWRanges.back().first < sortedWRanges.back().second);

#endif

    vi->splitStroke(i, sortedWRanges);

    UINT addedStroke =
        vi->getStrokeCount() - (oldStrokeSize - 1);  // -1 for the removed one

    i += addedStroke;

    *it = -1;
    m_indexes.insert(it, addedStroke - 1, -1);
    it = m_indexes.begin() + i;
  }

  invalidate();
}

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

void EraserTool::erase(TVectorImageP vi, TRectD &rect) {
  if (rect.x0 > rect.x1) std::swap(rect.x1, rect.x0);
  if (rect.y0 > rect.y1) std::swap(rect.y1, rect.y0);
  int i     = 0;
  int index = TTool::getApplication()->getCurrentLevelStyleIndex();
  std::vector<int> eraseStrokes;

  TXshSimpleLevel *level =
      TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
  // Release the previous undo if it exists
  if (m_undo) {
    delete m_undo;
    m_undo = nullptr;
  }
  m_undo = new UndoEraser(level, getCurrentFid());
  for (i = 0; i < (int)vi->getStrokeCount(); i++) {
    if (!vi->inCurrentGroup(i)) continue;
    TStroke *stroke = vi->getStroke(i);
    if (!m_invertOption.getValue()) {
      if ((!m_selective.getValue() || stroke->getStyle() == index) &&
          rect.contains(stroke->getBBox())) {
        m_undo->addOldStroke(i, vi->getVIStroke(i));
        eraseStrokes.push_back(i);
      }
    } else {
      if ((!m_selective.getValue() || stroke->getStyle() == index) &&
          !rect.contains(stroke->getBBox())) {
        m_undo->addOldStroke(i, vi->getVIStroke(i));
        eraseStrokes.push_back(i);
      }
    }
  }
  for (i = (int)eraseStrokes.size() - 1; i >= 0; i--)
    vi->deleteStroke(eraseStrokes[i]);
  TUndoManager::manager()->add(m_undo);
  m_undo = nullptr;
  invalidate();
}

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

void EraserTool::stopErase(TVectorImageP vi) {
  assert(m_undo != nullptr);

  UINT size = m_indexes.size();

  assert(size == vi->getStrokeCount());
  UINT i = 0;
  for (; i < size; i++) {
    if (m_indexes[i] == -1) m_undo->addNewStroke(i, vi->getVIStroke(i));
  }
  TUndoManager::manager()->add(m_undo);
  m_undo   = nullptr;
  m_active = false;
  invalidate();
  notifyImageChanged();
}

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

void EraserTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) {
  m_brushPos = m_mousePos = pos;

  m_active = true;

  TImageP image(getImage(true));
  m_activeImage = image;
  if (m_eraseType.getValue() == NORMAL_ERASE) {
    if (TVectorImageP vi = image) startErase(vi, pos);
  } else if (m_eraseType.getValue() == RECT_ERASE) {
    m_selectingRect.x0 = pos.x;
    m_selectingRect.y0 = pos.y;
    m_selectingRect.x1 = pos.x + 1;
    m_selectingRect.y1 = pos.y + 1;
    invalidate();
  } else if (m_eraseType.getValue() == FREEHAND_ERASE ||
             m_eraseType.getValue() == SEGMENT_ERASE) {
    startFreehand(pos);
  } else if (m_eraseType.getValue() == POLYLINE_ERASE) {
    addPointPolyline(pos);
  }
}

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

void EraserTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) {
  m_brushPos = m_mousePos = pos;

  if (!m_active) return;

  TImageP image(getImage(true));
  if (m_eraseType.getValue() == RECT_ERASE) {
    m_selectingRect.x1 = pos.x;
    m_selectingRect.y1 = pos.y;
    invalidate();
    return;
  } else if (m_eraseType.getValue() == NORMAL_ERASE) {
    if (!m_undo) leftButtonDown(pos, e);
    if (TVectorImageP vi = image) erase(vi, pos);
  } else if (m_eraseType.getValue() == FREEHAND_ERASE ||
             m_eraseType.getValue() == SEGMENT_ERASE) {
    freehandDrag(pos);
  }
}

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

void EraserTool::multiEraseRect(TFrameId firstFrameId, TFrameId lastFrameId,
                                TRectD firstRect, TRectD lastRect,
                                bool invert) {
  int r0 = firstFrameId.getNumber();
  int r1 = lastFrameId.getNumber();

  if (r0 > r1) {
    std::swap(r0, r1);
    std::swap(firstFrameId, lastFrameId);
    std::swap(firstRect, lastRect);
  }
  if ((r1 - r0) < 1) return;

  std::vector<TFrameId> allFids;
  m_level->getFids(allFids);
  std::vector<TFrameId>::iterator i0 = allFids.begin();
  while (i0 != allFids.end() && *i0 < firstFrameId) i0++;
  if (i0 == allFids.end()) return;
  std::vector<TFrameId>::iterator i1 = i0;
  while (i1 != allFids.end() && *i1 <= lastFrameId) i1++;
  assert(i0 < i1);
  std::vector<TFrameId> fids(i0, i1);
  int m = fids.size();
  assert(m > 0);

  enum TInbetween::TweenAlgorithm algorithm = TInbetween::LinearInterpolation;
  if (m_interpolation.getValue() == EASE_IN_INTERPOLATION) {
    algorithm = TInbetween::EaseInInterpolation;
  } else if (m_interpolation.getValue() == EASE_OUT_INTERPOLATION) {
    algorithm = TInbetween::EaseOutInterpolation;
  } else if (m_interpolation.getValue() == EASE_IN_OUT_INTERPOLATION) {
    algorithm = TInbetween::EaseInOutInterpolation;
  }

  TUndoManager::manager()->beginBlock();
  for (int i = 0; i < m; ++i) {
    TFrameId fid = fids[i];
    assert(firstFrameId <= fid && fid <= lastFrameId);
    TVectorImageP img = (TVectorImageP)m_level->getFrame(fid, true);
    assert(img);
    double t    = m > 1 ? (double)i / (double)(m - 1) : 0.5;
    t           = TInbetween::interpolation(t, algorithm);
    TRectD rect = interpolateRect(firstRect, lastRect, t);
    // m_level->setFrame(fid, img); // Necessary: if getFrame uncompressed a
    // compressed image, without setFrame the modifications made here would be
    // lost.

    // Set fid as current to notify the image change
    TTool::Application *app = TTool::getApplication();
    if (app) {
      if (app->getCurrentFrame()->isEditingScene())
        app->getCurrentFrame()->setFrame(fid.getNumber() - 1);
      else
        app->getCurrentFrame()->setFid(fid);
    }

    erase(img, rect);

    notifyImageChanged();
  }
  TUndoManager::manager()->endBlock();
}

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

void EraserTool::onImageChanged() {
  if (m_active) {
    stopErase(m_activeImage);
  }
  if (!m_multi.getValue()) return;
  TTool::Application *application = TTool::getApplication();
  if (!application) return;
  TXshSimpleLevel *xshl = nullptr;
  if (application->getCurrentLevel()->getLevel())
    xshl = application->getCurrentLevel()->getLevel()->getSimpleLevel();

  if (!xshl || m_level.getPointer() != xshl ||
      (m_eraseType.getValue() == RECT_ERASE && m_selectingRect.isEmpty()) ||
      ((m_eraseType.getValue() == FREEHAND_ERASE ||
        m_eraseType.getValue() == POLYLINE_ERASE ||
        m_eraseType.getValue() == SEGMENT_ERASE) &&
       !m_firstStroke))
    resetMulti();
  else if (m_firstFrameId == getCurrentFid())
    m_firstFrameSelected = false;  // If I passed to state 1 and return to the
                                   // initial image, return to initial state
  else {                           // Change state.
    m_firstFrameSelected = true;
    if (m_eraseType.getValue() == RECT_ERASE) {
      assert(!m_selectingRect.isEmpty());
      m_firstRect = m_selectingRect;
    }
  }
}

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

void EraserTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e) {
  if (!m_active) return;
  m_active = false;
  TImageP image(getImage(true));
  TVectorImageP vi = image;

  TTool::Application *application = TTool::getApplication();
  if (!vi || !application) return;
  if (m_eraseType.getValue() == NORMAL_ERASE) {
    if (!m_undo) leftButtonDown(pos, e);
    stopErase(vi);
  } else if (m_eraseType.getValue() == RECT_ERASE) {
    if (m_selectingRect.x0 > m_selectingRect.x1)
      std::swap(m_selectingRect.x1, m_selectingRect.x0);
    if (m_selectingRect.y0 > m_selectingRect.y1)
      std::swap(m_selectingRect.y1, m_selectingRect.y0);

    if (m_multi.getValue()) {
      if (m_firstFrameSelected) {
        multiEraseRect(m_firstFrameId, getCurrentFid(), m_firstRect,
                       m_selectingRect, m_invertOption.getValue());
        if (e.isShiftPressed()) {
          m_firstRect    = m_selectingRect;
          m_firstFrameId = getCurrentFid();
        } else {
          if (application->getCurrentFrame()->isEditingScene()) {
            application->getCurrentColumn()->setColumnIndex(m_currCell.first);
            application->getCurrentFrame()->setFrame(m_currCell.second);
          } else
            application->getCurrentFrame()->setFid(m_veryFirstFrameId);
          resetMulti();
        }
        invalidate();
      } else {
        if (application->getCurrentFrame()->isEditingScene())
          m_currCell = std::pair<int, int>(
              application->getCurrentColumn()->getColumnIndex(),
              application->getCurrentFrame()->getFrame());
      }
      return;
    } else {
      erase(vi, m_selectingRect);
      notifyImageChanged();
      m_selectingRect.empty();
    }
  } else if (m_eraseType.getValue() == FREEHAND_ERASE) {
    closeFreehand(pos);
    if (m_multi.getValue()) {
      multiErase(m_stroke, e, &EraserTool::eraseRegion);
      invalidate();
    } else {
      eraseRegion(vi, m_stroke);
      invalidate();
      notifyImageChanged();
    }
    m_track.clear();
  } else if (m_eraseType.getValue() == SEGMENT_ERASE) {
    double error = (30.0 / 11) * sqrt(getPixelSize() * getPixelSize());
    m_stroke     = m_track.makeStroke(error);
    m_stroke->setStyle(1);
    if (m_multi.getValue()) {
      multiErase(m_stroke, e, &EraserTool::eraseSegments);
      invalidate();
    } else {
      eraseSegments(vi, m_stroke);
      invalidate();
      notifyImageChanged();
    }
    m_track.clear();
  }
}

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

//! The polyline is closed and erasing begins.
/*! A stroke representing the drawn polyline is created.
  If "frame range" erase method is selected, \b multiEraserRegion is called,
  otherwise \b eraseRegion is called*/
void EraserTool::leftButtonDoubleClick(const TPointD &pos,
                                       const TMouseEvent &e) {
  TVectorImageP vi = getImage(true);
  if (m_eraseType.getValue() == POLYLINE_ERASE) {
    closePolyline(pos);

    std::vector<TThickPoint> strokePoints;
    for (UINT i = 0; i < m_polyline.size() - 1; i++) {
      strokePoints.push_back(TThickPoint(m_polyline[i], 1));
      strokePoints.push_back(
          TThickPoint(0.5 * (m_polyline[i] + m_polyline[i + 1]), 1));
    }
    strokePoints.push_back(TThickPoint(m_polyline.back(), 1));
    m_polyline.clear();

    std::unique_ptr<TStroke> stroke(new TStroke(strokePoints));
    assert(stroke->getPoint(0) == stroke->getPoint(1));

    if (m_multi.getValue()) {
      multiErase(stroke.get(), e, &EraserTool::eraseRegion);
    } else {
      eraseRegion(vi, stroke.get());
      m_active = false;
      notifyImageChanged();
    }
    // The unique_ptr will be automatically released when it goes out of scope
    invalidate();
  }
}

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

void EraserTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
  struct Locals {
    EraserTool *m_this;

    void setValue(TDoubleProperty &prop, double value) {
      prop.setValue(value);

      m_this->onPropertyChanged(prop.getName());
      TTool::getApplication()->getCurrentTool()->notifyToolChanged();
    }

    void addValue(TDoubleProperty &prop, double add) {
      const TDoubleProperty::Range &range = prop.getRange();
      setValue(prop, tcrop(prop.getValue() + add, range.first, range.second));
    }

  } locals = {this};

  switch (e.getModifiersMask()) {
  case TMouseEvent::ALT_KEY: {
    // User wants to alter the maximum brush size
    const TPointD &diff = pos - m_mousePos;
    double add          = (fabs(diff.x) > fabs(diff.y)) ? diff.x : diff.y;

    locals.addValue(m_toolSize, add);
    break;
  }

  default:
    m_brushPos = pos;
    break;
  }

  m_oldMousePos = m_mousePos = pos;
  invalidate();
}

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

bool EraserTool::onPropertyChanged(std::string propertyName) {
  EraseVectorType          = ::to_string(m_eraseType.getValue());
  EraseVectorInterpolation = ::to_string(m_interpolation.getValue());
  EraseVectorSize          = m_toolSize.getValue();
  EraseVectorSelective     = m_selective.getValue();
  EraseVectorInvert        = m_invertOption.getValue();
  EraseVectorRange         = m_multi.getValue();

  double x = m_toolSize.getValue();

  double minRange = 1;
  double maxRange = 100;

  double minSize = 2;
  double maxSize = 100;

  m_pointSize =
      ((x - minRange) / (maxRange - minRange) * (maxSize - minSize) + minSize) *
      0.5;
  invalidate();

  return true;
}

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

void EraserTool::onEnter() {
  if (m_firstTime) {
    m_toolSize.setValue(EraseVectorSize);
    m_eraseType.setValue(::to_wstring(EraseVectorType.getValue()));
    m_interpolation.setValue(::to_wstring(EraseVectorInterpolation.getValue()));
    m_selective.setValue(EraseVectorSelective ? 1 : 0);
    m_invertOption.setValue(EraseVectorInvert ? 1 : 0);
    m_multi.setValue(EraseVectorRange ? 1 : 0);
    m_firstTime = false;
  }

  double x = m_toolSize.getValue();

  double minRange = 1;
  double maxRange = 100;

  double minSize = 2;
  double maxSize = 100;

  m_pointSize =
      ((x - minRange) / (maxRange - minRange) * (maxSize - minSize) + minSize) *
      0.5;

  //  getApplication()->editImage();
}

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

void EraserTool::onLeave() {
  draw();
  m_pointSize = -1;
}

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

void EraserTool::onActivate() {
  resetMulti();
  m_polyline.clear();
  onEnter();
}

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

//! \b pos is added to \b m_track and the first piece of the freehand is drawn.
//! \b m_firstPos is initialized
void EraserTool::startFreehand(const TPointD &pos) {
  m_track.clear();
  m_firstPos = pos;
  m_track.add(TThickPoint(pos, m_thick), getPixelSize() * getPixelSize());
}

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

//! \b pos is added to \b m_track and another piece of the freehand is drawn.
void EraserTool::freehandDrag(const TPointD &pos) {
#if defined(MACOSX)
//  m_viewer->enableRedraw(false);
#endif
  m_track.add(TThickPoint(pos, m_thick), getPixelSize() * getPixelSize());
  invalidate(m_track.getModifiedRegion());
}

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

//! The freehand is closed (last point added to m_track) and a stroke
//! representing the freehand is created.
void EraserTool::closeFreehand(const TPointD &pos) {
#if defined(MACOSX)
//  m_viewer->enableRedraw(true);
#endif
  if (m_track.isEmpty()) return;
  m_track.add(TThickPoint(m_firstPos, m_thick),
              getPixelSize() * getPixelSize());
  m_track.filterPoints();
  double error = (30.0 / 11) * sqrt(getPixelSize() * getPixelSize());

  // Release the previous stroke if it exists
  if (m_stroke) {
    delete m_stroke;
  }
  m_stroke = m_track.makeStroke(error);
  m_stroke->setStyle(1);
}

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

//! A point is added to the m_polyline vector.
void EraserTool::addPointPolyline(const TPointD &pos) {
  m_firstPos = pos;
  m_polyline.push_back(pos);
}

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

//! Adds the last pos to \b m_polyline and closes the polyline (adds
//! \b m_polyline.front() to the end of the vector)
void EraserTool::closePolyline(const TPointD &pos) {
  if (m_polyline.size() <= 1) return;
  if (m_polyline.back() != pos) m_polyline.push_back(pos);
  if (m_polyline.back() != m_polyline.front())
    m_polyline.push_back(m_polyline.front());
  invalidate();
}

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

static bool doublePairCompare(DoublePair p1, DoublePair p2) {
  return p1.first < p2.first;
}

void EraserTool::eraseSegments(const TVectorImageP vi, TStroke *eraseStroke) {
  if (!vi || !eraseStroke) return;

  int strokeNumber = vi->getStrokeCount();
  int colorStyle   = TTool::getApplication()->getCurrentLevelStyleIndex();
  std::vector<int> touchedStrokeIndex;
  std::vector<std::vector<double>> touchedStrokeW;
  std::vector<std::vector<DoublePair>> touchedStrokeRanges;

  // find all touched strokes and where it is touched
  for (int i = 0; i < strokeNumber; ++i) {
    std::vector<DoublePair> intersections;
    std::vector<double> ws;
    TStroke *stroke = vi->getStroke(i);
    bool touched    = false;

    if (m_selective.getValue() && stroke->getStyle() != colorStyle) {
      continue;
    }

    intersect(eraseStroke, stroke, intersections, false);

    for (auto &intersection : intersections) {
      touched = true;
      ws.push_back(intersection.second);
    }

    if (touched) {
      touchedStrokeIndex.push_back(i);
      touchedStrokeW.push_back(ws);
    }
  }

  // if the eraser did not touch any strokes, return
  if (touchedStrokeIndex.size() == 0) {
    return;
  }

  // find closest intersections of each end of the touched place of each stroke
  for (int i = 0; i < touchedStrokeIndex.size(); ++i) {
    std::vector<DoublePair> range;
    for (auto w : touchedStrokeW[i]) {
      std::vector<DoublePair> intersections;
      double lowerW = 0.0, higherW = 1.0;
      double higherW0 = 1.0,
             lowerW1  = 0.0;  // these two value are used when the stroke is
                              // self-loop-ed, it assumes touched W is 0 or 1 to
                              // find closet intersection

      int strokeIndex = touchedStrokeIndex[i];
      TStroke *stroke = vi->getStroke(strokeIndex);

      // check self intersection first
      intersect(stroke, stroke, intersections, false);
      for (auto &intersection : intersections) {
        if (areAlmostEqual(intersection.first, 0, 1e-6)) {
          continue;
        }
        if (areAlmostEqual(intersection.second, 1, 1e-6)) {
          continue;
        }

        if (intersection.first < w) {
          lowerW = std::max(lowerW, intersection.first);
        } else {
          higherW = std::min(higherW, intersection.first);
        }

        if (intersection.second < w) {
          lowerW = std::max(lowerW, intersection.second);
        } else {
          higherW = std::min(higherW, intersection.second);
        }

        lowerW1  = std::max(lowerW1, intersection.first);
        higherW0 = std::min(higherW0, intersection.first);
        lowerW1  = std::max(lowerW1, intersection.second);
        higherW0 = std::min(higherW0, intersection.second);
      }

      // then check intersection with other strokes
      for (int j = 0; j < strokeNumber; ++j) {
        if (j == strokeIndex) {
          continue;
        }

        TStroke *intersectedStroke = vi->getStroke(j);
        intersect(stroke, intersectedStroke, intersections, false);
        for (auto &intersection : intersections) {
          if (intersection.first < w) {
            lowerW = std::max(lowerW, intersection.first);
          } else {
            higherW = std::min(higherW, intersection.first);
          }
          lowerW1  = std::max(lowerW1, intersection.first);
          higherW0 = std::min(higherW0, intersection.first);
        }
      }

      range.push_back(std::make_pair(lowerW, higherW));
      if (stroke->isSelfLoop()) {
        if (lowerW == 0.0) {
          range.push_back(std::make_pair(lowerW1, 1.0));
        } else if (higherW == 1.0) {
          range.push_back(std::make_pair(0.0, higherW0));
        }
      }
    }
    touchedStrokeRanges.push_back(range);
  }

  // merge all ranges of the same stroke by using interval merging algorithm
  for (auto &ranges : touchedStrokeRanges) {
    std::vector<DoublePair> merged;

    std::sort(ranges.begin(), ranges.end(), doublePairCompare);

    merged.push_back(ranges[0]);
    for (auto &range : ranges) {
      if (merged.back().second < range.first &&
          !areAlmostEqual(merged.back().second, range.first, 1e-3)) {
        merged.push_back(range);
      } else if (merged.back().second < range.second) {
        merged.back().second = range.second;
      }
    }

    ranges = merged;
  }

  // create complement range
  for (auto &ranges : touchedStrokeRanges) {
    std::vector<DoublePair> complement;

    double last = 0.0;
    for (auto &range : ranges) {
      if (!areAlmostEqual(last, range.first, 1e-3)) {
        complement.push_back(std::make_pair(last, range.first));
      }
      last = range.second;
    }

    if (!areAlmostEqual(last, 1.0, 1e-3)) {
      complement.push_back(std::make_pair(last, 1.0));
    }
    ranges = complement;
  }

  // calculate how many lines are added for calculating the final index of added
  // strokes
  int added = 0;
  for (int i = touchedStrokeIndex.size() - 1; i >= 0; --i) {
    bool willbeJoined = vi->getStroke(touchedStrokeIndex[i])->isSelfLoop() &&
                        touchedStrokeRanges[i][0].first == 0.0 &&
                        touchedStrokeRanges[i].back().second == 1.0;

    added += touchedStrokeRanges[i].size();
    if (willbeJoined) {
      --added;
    }
  }

  // do the erasing and construct the undo action
  TXshSimpleLevel *level =
      TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
  // Release the previous stroke if it exists
  if (m_undo) {
    delete m_undo;
    m_undo = nullptr;
  }
  UndoEraser *undo = new UndoEraser(level, getCurrentFid());
  for (int i = touchedStrokeIndex.size() - 1; i >= 0; --i) {
    undo->addOldStroke(touchedStrokeIndex[i],
                       vi->getVIStroke(touchedStrokeIndex[i]));

    if (touchedStrokeRanges[i].size() == 0) {
      vi->deleteStroke(touchedStrokeIndex[i]);
    } else {
      bool willbeJoined = vi->getStroke(touchedStrokeIndex[i])->isSelfLoop() &&
                          touchedStrokeRanges[i][0].first == 0.0 &&
                          touchedStrokeRanges[i].back().second == 1.0;

      vi->splitStroke(touchedStrokeIndex[i], touchedStrokeRanges[i]);

      int size = touchedStrokeRanges[i].size();
      if (willbeJoined) {
        --size;
      }
      added -= size;
      for (int j = 0; j < size; ++j) {
        int finalIndex   = touchedStrokeIndex[i] + j - i + added;
        int currentIndex = touchedStrokeIndex[i] + j;
        undo->addNewStroke(finalIndex, vi->getVIStroke(currentIndex));
      }
    }
  }

  TUndoManager::manager()->add(undo);
  m_undo = nullptr;
  return;
}

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

//! Erases strokes present in \b vi and contained in the region delimited by \b
//! stroke.
/*!Strokes to be erased are searched with the necessary checks on parameters \b
   m_invertOption and \b m_selective.
        If a stroke should be erased, it is inserted in \b eraseStrokes.
        All strokes are erased at the end.*/
void EraserTool::eraseRegion(const TVectorImageP vi, TStroke *stroke) {
  if (!vi || !stroke) return;
  TVectorImage eraseImg;
  TStroke *eraseStroke = new TStroke(*stroke);
  eraseImg.addStroke(eraseStroke);
  eraseImg.findRegions();
  int strokeIndex, regionIndex, colorStyle;
  colorStyle = TTool::getApplication()->getCurrentLevelStyleIndex();
  std::vector<int> eraseStrokes;

  TXshSimpleLevel *level =
      TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
  // Release the previous stroke if it exists
  if (m_undo) {
    delete m_undo;
    m_undo = nullptr;
  }
  m_undo = new UndoEraser(level, getCurrentFid());

  if (!m_invertOption.getValue()) {
    for (strokeIndex = 0; strokeIndex < (int)vi->getStrokeCount();
         strokeIndex++) {
      if (!vi->inCurrentGroup(strokeIndex)) continue;
      TStroke *currentStroke = vi->getStroke(strokeIndex);
      for (regionIndex = 0; regionIndex < (int)eraseImg.getRegionCount();
           regionIndex++) {
        TRegion *region = eraseImg.getRegion(regionIndex);
        if ((!m_selective.getValue() ||
             (m_selective.getValue() &&
              currentStroke->getStyle() == colorStyle)) &&
            region->contains(*currentStroke, true)) {
          eraseStrokes.push_back(strokeIndex);
          m_undo->addOldStroke(strokeIndex, vi->getVIStroke(strokeIndex));
        }
      }
    }
  } else {
    for (strokeIndex = 0; strokeIndex < (int)vi->getStrokeCount();
         strokeIndex++) {
      TStroke *currentStroke = vi->getStroke(strokeIndex);
      bool eraseIt           = false;
      for (regionIndex = 0; regionIndex < (int)eraseImg.getRegionCount();
           regionIndex++) {
        TRegion *region = eraseImg.getRegion(regionIndex);
        if (!m_selective.getValue() ||
            (m_selective.getValue() && currentStroke->getStyle() == colorStyle))
          eraseIt = true;
        if (region->contains(*currentStroke, true)) {
          eraseIt = false;
          break;
        }
      }
      if (eraseIt) {
        m_undo->addOldStroke(strokeIndex, vi->getVIStroke(strokeIndex));
        eraseStrokes.push_back(strokeIndex);
      }
    }
  }
  int i;
  for (i = (int)eraseStrokes.size() - 1; i >= 0; i--)
    vi->deleteStroke(eraseStrokes[i]);
  TUndoManager::manager()->add(m_undo);
  m_undo = nullptr;
}

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

//! doErase is called on frames between \b firstFrameId and \b lastFrameId.
/*!Vector \b fids is loaded with TFrameIds of images on which to perform
 * erasure.*/
void EraserTool::doMultiErase(TFrameId &firstFrameId, TFrameId &lastFrameId,
                              const TStroke *firstStroke,
                              const TStroke *lastStroke,
                              EraserTool::EraseFunction eraseFunction) {
  TXshSimpleLevelP sl =
      TTool::getApplication()->getCurrentLevel()->getLevel()->getSimpleLevel();

  std::unique_ptr<TStroke> first(new TStroke());
  std::unique_ptr<TStroke> last(new TStroke());
  *first = *firstStroke;
  *last  = *lastStroke;

  TVectorImageP firstImage = new TVectorImage();
  TVectorImageP lastImage  = new TVectorImage();
  firstImage->addStroke(first.release());  // Transfere posse para a imagem
  lastImage->addStroke(last.release());    // Transfere posse para a imagem

  bool backward = false;
  if (firstFrameId > lastFrameId) {
    std::swap(firstFrameId, lastFrameId);
    backward = true;
  }
  assert(firstFrameId <= lastFrameId);

  std::vector<TFrameId> allFids;
  sl->getFids(allFids);
  std::vector<TFrameId>::iterator i0 = allFids.begin();
  while (i0 != allFids.end() && *i0 < firstFrameId) i0++;
  if (i0 == allFids.end()) return;
  std::vector<TFrameId>::iterator i1 = i0;
  while (i1 != allFids.end() && *i1 <= lastFrameId) i1++;
  assert(i0 < i1);
  std::vector<TFrameId> fids(i0, i1);
  int m = fids.size();
  assert(m > 0);

  // Find the starting frame for the current level in the XSheet.
  TTool::Application *app = TTool::getApplication();
  int startRowInXSheet = 0, endRowInXSheet = 0;
  if (app && app->getCurrentFrame()->isEditingScene()) {
    int currentRow     = getFrame();
    TXsheet *xSheet    = getXsheet();
    TXshColumn *column = xSheet->getColumn(getColumnIndex());
    column->getLevelRange(currentRow, startRowInXSheet, endRowInXSheet);
  }

  enum TInbetween::TweenAlgorithm algorithm = TInbetween::LinearInterpolation;
  if (m_interpolation.getValue() == EASE_IN_INTERPOLATION) {
    algorithm = TInbetween::EaseInInterpolation;
  } else if (m_interpolation.getValue() == EASE_OUT_INTERPOLATION) {
    algorithm = TInbetween::EaseOutInterpolation;
  } else if (m_interpolation.getValue() == EASE_IN_OUT_INTERPOLATION) {
    algorithm = TInbetween::EaseInOutInterpolation;
  }

  TUndoManager::manager()->beginBlock();
  for (int i = 0; i < m; ++i) {
    TFrameId fid = fids[i];
    assert(firstFrameId <= fid && fid <= lastFrameId);
    double t = m > 1 ? (double)i / (double)(m - 1) : 0.5;
    t        = TInbetween::interpolation(t, algorithm);
    // Set the fid as current to notify the image change
    if (app) {
      if (app->getCurrentFrame()->isEditingScene())
        app->getCurrentFrame()->setFrame(startRowInXSheet + fid.getNumber() -
                                         1);
      else
        app->getCurrentFrame()->setFid(fid);
    }
    doErase(backward ? 1 - t : t, sl, fid, firstImage, lastImage,
            eraseFunction);
    notifyImageChanged();
  }
  TUndoManager::manager()->endBlock();
}

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

//! \b eraseRegion is called for the correct frame in "Frame Range" mode.
/*!In cases where \b t is different from zero and one, a new \b TVectorImageP is
   generated by calling \b TInbetween.
        The new image contains the stroke to give to eraseRegion. \b fid is the
   TFrameId of the image on which
        erasure should be performed.*/
void EraserTool::doErase(double t, const TXshSimpleLevelP &sl,
                         const TFrameId &fid, const TVectorImageP &firstImage,
                         const TVectorImageP &lastImage,
                         EraserTool::EraseFunction eraseFunction) {
  TVectorImageP img = sl->getFrame(fid, true);
  if (t == 0)
    (this->*eraseFunction)(img, firstImage->getStroke(0));
  else if (t == 1)
    (this->*eraseFunction)(img, lastImage->getStroke(0));
  else {
    assert(firstImage->getStrokeCount() == 1);
    assert(lastImage->getStrokeCount() == 1);
    TVectorImageP vi = TInbetween(firstImage, lastImage).tween(t);
    assert(vi->getStrokeCount() == 1);
    (this->*eraseFunction)(img, vi->getStroke(0));
  }
}

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

//! Performs erasure in "Frame range" mode.
/*! If the first frame has already been selected, call \b doMultiErase;
 otherwise \b m_firstStroke is initialized.*/
void EraserTool::multiErase(TStroke *stroke, const TMouseEvent &e,
                            EraserTool::EraseFunction eraseFunction) {
  TTool::Application *application = TTool::getApplication();
  if (!application) return;

  if (m_firstFrameSelected) {
    if (m_firstStroke && stroke) {
      TFrameId tmpFrameId = getCurrentFid();
      doMultiErase(m_firstFrameId, tmpFrameId, m_firstStroke, stroke,
                   eraseFunction);
    }
    if (e.isShiftPressed()) {
      // Release the previous stroke if it exists
      if (m_firstStroke) {
        delete m_firstStroke;
      }
      m_firstStroke  = new TStroke(*stroke);
      m_firstFrameId = getCurrentFid();
    } else {
      if (application->getCurrentFrame()->isEditingScene()) {
        application->getCurrentColumn()->setColumnIndex(m_currCell.first);
        application->getCurrentFrame()->setFrame(m_currCell.second);
      } else
        application->getCurrentFrame()->setFid(m_veryFirstFrameId);
      resetMulti();
    }
  } else {
    // Release the previous stroke if it exists
    if (m_firstStroke) {
      delete m_firstStroke;
    }
    m_firstStroke = new TStroke(*stroke);
    if (application->getCurrentFrame()->isEditingScene())
      m_currCell =
          std::pair<int, int>(application->getCurrentColumn()->getColumnIndex(),
                              application->getCurrentFrame()->getFrame());
  }
}

//-----------------------------------------------------------------------------
/*! When the tool is switched during dragging, Erase end processing is performed
 */
void EraserTool::onDeactivate() {
  if (!m_active) return;

  m_active = false;

  // TODO: Finishing erase procedure is only available for normal type.
  // Supporting other types is needed. 2016/1/22 shun_iwasawa
  if (m_eraseType.getValue() != NORMAL_ERASE) return;

  TImageP image(getImage(true));
  TVectorImageP vi                = image;
  TTool::Application *application = TTool::getApplication();
  if (!vi || !application) return;

  stopErase(vi);
}

// TTool *getEraserTool() {return &eraserTool;}
