

// TnzCore includes
#include "tpalette.h"
#include "tstroke.h"
#include "tvectorimage.h"
#include "texception.h"
#include "tvectorrenderdata.h"
#include "tconvert.h"
#include "tofflinegl.h"
#include "tpixelutils.h"
#include "tcolorstyles.h"

//*****************************************************************************
//    Macros
//*****************************************************************************

#ifndef checkErrorsByGL
#define checkErrorsByGL                                                        \
  {                                                                            \
    GLenum err = glGetError();                                                 \
    assert(err != GL_INVALID_ENUM);                                            \
    assert(err != GL_INVALID_VALUE);                                           \
    assert(err != GL_INVALID_OPERATION);                                       \
    assert(err != GL_STACK_OVERFLOW);                                          \
    assert(err != GL_STACK_UNDERFLOW);                                         \
    assert(err != GL_OUT_OF_MEMORY);                                           \
    assert(err == GL_NO_ERROR);                                                \
  }
#endif

#undef checkErrorsByGL
#define checkErrorsByGL

//*****************************************************************************
//    TColorStyle  implementation
//*****************************************************************************

int TColorStyle::m_currentFrame = 0;

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

TColorStyle::TColorStyle()
    : m_name(L"color")
    , m_globalName(L"")
    , m_originalName(L"")
    , m_versionNumber(0)
    , m_flags(0)
    , m_enabled(true)
    , m_icon(0)
    , m_validIcon(false)
    , m_isEditedFromOriginal(false)
    , m_hash(0)
    , m_pickedPosition() {}

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

TColorStyle::~TColorStyle() {}

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

TColorStyle::TColorStyle(const TColorStyle &other)
    : m_name(other.m_name)
    , m_globalName(other.m_globalName)
    , m_originalName(other.m_originalName)
    , m_versionNumber(other.m_versionNumber)
    , m_flags(other.m_flags)
    , m_enabled(other.m_enabled)
    , m_validIcon(false)
    , m_isEditedFromOriginal(other.m_isEditedFromOriginal)
    , m_hash(other.m_hash)
    , m_pickedPosition(other.m_pickedPosition) {}

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

TColorStyle &TColorStyle::operator=(const TColorStyle &other) {
  m_name                 = other.m_name;
  m_globalName           = other.m_globalName;
  m_originalName         = other.m_originalName;
  m_versionNumber        = other.m_versionNumber;
  m_flags                = other.m_flags;
  m_enabled              = other.m_enabled;
  m_validIcon            = false;
  m_isEditedFromOriginal = other.m_isEditedFromOriginal;
  m_hash                 = other.m_hash;
  m_pickedPosition       = other.m_pickedPosition;

  return *this;
}

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

bool TColorStyle::operator==(const TColorStyle &cs) const {
  if (getTagId() != cs.getTagId()) return false;

  if (getMainColor() != cs.getMainColor()) return false;

  int paramCount = getParamCount();
  if (paramCount != cs.getParamCount()) return false;

  int colorParamCount = getColorParamCount();
  if (colorParamCount != cs.getColorParamCount()) return false;

  if (m_name != cs.getName()) return false;
  if (m_originalName != cs.getOriginalName()) return false;
  if (m_globalName != cs.getGlobalName()) return false;
  if (m_isEditedFromOriginal != cs.getIsEditedFlag()) return false;
  if (m_pickedPosition != cs.getPickedPosition()) return false;
  if (m_flags != cs.getFlags()) return false;

  for (int p = 0; p < colorParamCount; ++p)
    if (getColorParamValue(p) != cs.getColorParamValue(p)) return false;

  for (int p = 0; p < paramCount; ++p) {
    switch (getParamType(p)) {
    case BOOL:
      if (getParamValue(bool_tag(), p) != cs.getParamValue(bool_tag(), p))
        return false;
      break;
    case INT:
    case ENUM:
      if (getParamValue(int_tag(), p) != cs.getParamValue(int_tag(), p))
        return false;
      break;
    case DOUBLE:
      if (getParamValue(double_tag(), p) != cs.getParamValue(double_tag(), p))
        return false;
      break;
    case FILEPATH:
      if (getParamValue(TFilePath_tag(), p) !=
          cs.getParamValue(TFilePath_tag(), p))
        return false;
      break;
    default:
      assert(false);
    }
  }

  return true;
}

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

QString TColorStyle::getDescription() const {
  assert(false);
  return QString("");
}

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

QString TColorStyle::getParamNames(int index) const {
  assert(false);
  return QString("");
}

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

void TColorStyle::updateVersionNumber() { ++m_versionNumber; }

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

const TRaster32P &TColorStyle::getIcon(const TDimension &d) {
  checkErrorsByGL;
  if (!m_validIcon || !m_icon || m_icon->getSize() != d) {
    checkErrorsByGL;
    makeIcon(d);
    checkErrorsByGL;
    m_validIcon = true;
  }
  checkErrorsByGL;

  if (!m_icon) {
    checkErrorsByGL;
    TRaster32P icon(d);
    checkErrorsByGL;
    icon->fill(TPixel32::Black);
    checkErrorsByGL;
    int lx = icon->getLx();
    checkErrorsByGL;
    int ly = icon->getLy();
    checkErrorsByGL;
    for (int y = 0; y < ly; y++) {
      checkErrorsByGL;
      int x = ((lx - 1 - 10) * y / ly);
      checkErrorsByGL;
      icon->extractT(x, y, x + 5, y)->fill(TPixel32::Red);
      checkErrorsByGL;
    }
    checkErrorsByGL;
    m_icon = icon;
    checkErrorsByGL;
  }
  return m_icon;
}

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

void TColorStyle::makeIcon(const TDimension &d) {
  checkErrorsByGL;
  TColorStyle *style = this->clone();
  checkErrorsByGL;

  TPaletteP tmpPalette = new TPalette();
  checkErrorsByGL;
  int id = tmpPalette->addStyle(style);
  checkErrorsByGL;

  int contextLx = pow(2.0, tceil(log((double)d.lx) / log(2.0)));
  int contextLy = pow(2.0, tceil(log((double)d.ly) / log(2.0)));
  TDimension dim(contextLx, contextLy);

  TOfflineGL *glContext = TOfflineGL::getStock(dim);

  checkErrorsByGL;
  glContext->clear(TPixel32::White);
  checkErrorsByGL;

  TVectorImageP img = new TVectorImage;
  checkErrorsByGL;
  img->setPalette(tmpPalette.getPointer());
  checkErrorsByGL;

  std::vector<TThickPoint> points(3);

  if (isRegionStyle() && !isStrokeStyle()) {
    points[0]        = TThickPoint(-55, -50, 1);
    points[1]        = TThickPoint(0, -60, 1);
    points[2]        = TThickPoint(55, -50, 1);
    TStroke *stroke1 = new TStroke(points);

    img->addStroke(stroke1);

    points[0]        = TThickPoint(50, -55, 1);
    points[1]        = TThickPoint(60, 0, 1);
    points[2]        = TThickPoint(50, 55, 1);
    TStroke *stroke2 = new TStroke(points);
    img->addStroke(stroke2);

    points[0]        = TThickPoint(55, 50, 1);
    points[1]        = TThickPoint(0, 60, 1);
    points[2]        = TThickPoint(-55, 50, 1);
    TStroke *stroke3 = new TStroke(points);
    img->addStroke(stroke3);

    points[0]        = TThickPoint(-50, 55, 1);
    points[1]        = TThickPoint(-60, 0, 1);
    points[2]        = TThickPoint(-50, -55, 1);
    TStroke *stroke4 = new TStroke(points);
    img->addStroke(stroke4);

    img->fill(TPointD(0, 0), id);
  } else if (isStrokeStyle() && !isRegionStyle()) {
    double rasX05 = d.lx * 0.5;
    double rasY05 = d.ly * 0.5;

    points[0]        = TThickPoint(-rasX05, -rasY05, 7);
    points[1]        = TThickPoint(0, -rasY05, 9);
    points[2]        = TThickPoint(rasX05, rasY05, 12);
    TStroke *stroke1 = new TStroke(points);

    stroke1->setStyle(id);

    img->addStroke(stroke1);
    points.clear();
  } else if (!isRasterStyle()) {
    assert(isStrokeStyle() && isRegionStyle());

    points[0]        = TThickPoint(-60, -30, 0.5);
    points[1]        = TThickPoint(0, -30, 0.5);
    points[2]        = TThickPoint(60, -30, 0.5);
    TStroke *stroke1 = new TStroke(points);
    stroke1->setStyle(id);
    img->addStroke(stroke1);

    points[0]        = TThickPoint(60, -30, 0.5);
    points[1]        = TThickPoint(60, 0, 0.5);
    points[2]        = TThickPoint(60, 30, 0.5);
    TStroke *stroke2 = new TStroke(points);
    stroke2->setStyle(id);
    img->addStroke(stroke2);

    points[0]        = TThickPoint(60, 30, 0.5);
    points[1]        = TThickPoint(0, 30, 0.5);
    points[2]        = TThickPoint(-60, 30, 0.5);
    TStroke *stroke3 = new TStroke(points);
    stroke3->setStyle(id);
    img->addStroke(stroke3);

    points[0]        = TThickPoint(-60, 30, 0.5);
    points[1]        = TThickPoint(-60, 0, 0.5);
    points[2]        = TThickPoint(-60, -30, 0.5);
    TStroke *stroke4 = new TStroke(points);
    stroke4->setStyle(id);
    img->addStroke(stroke4);

    img->fill(TPointD(0, 0), id);
  }

  TRectD bbox = img->getBBox();
  checkErrorsByGL;

  bbox = bbox.enlarge(TDimensionD(-10, -10));
  checkErrorsByGL;

  double scx  = 0.9 * d.lx / bbox.getLx();
  double scy  = 0.9 * d.ly / bbox.getLy();
  double sc   = std::min(scx, scy);
  double dx   = (d.lx - bbox.getLx() * sc) * 0.5;
  double dy   = (d.ly - bbox.getLy() * sc) * 0.5;
  TAffine aff = TScale(sc) * TTranslation(-bbox.getP00() + TPointD(dx, dy));

  checkErrorsByGL;
  if (isRegionStyle() && !isStrokeStyle()) aff = aff * TTranslation(-10, -10);

  checkErrorsByGL;
  const TVectorRenderData rd(aff, TRect(), tmpPalette.getPointer(), 0, true);
  checkErrorsByGL;
  glContext->draw(img, rd);
  checkErrorsByGL;

  TRect rect(d);
  if (!m_icon || m_icon->getSize() != d) {
    checkErrorsByGL;
    m_icon = glContext->getRaster()->extract(rect)->clone();
  } else {
    checkErrorsByGL;
    m_icon->copy(glContext->getRaster()->extract(rect));
  }
}

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

void TColorStyle::assignNames(const TColorStyle *src) {
  m_name                 = src->getName();
  m_globalName           = src->getGlobalName();
  m_originalName         = src->getOriginalName();
  m_isEditedFromOriginal = src->getIsEditedFlag();
}

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

void TColorStyle::assignBlend(const TColorStyle &a, const TColorStyle &b,
                              double t) {
  // Blend colors
  {
    int col, colCount = getColorParamCount();
    assert(a.getColorParamCount() == colCount &&
           b.getColorParamCount() == colCount);

    for (col = 0; col != colCount; ++col)
      setColorParamValue(
          col, blend(a.getColorParamValue(col), b.getColorParamValue(col), t));
  }

  // Blend parameters
  {
    int par, parCount = getParamCount();
    assert(a.getParamCount() == parCount && b.getParamCount() == parCount);

    for (par = 0; par != parCount; ++par) {
      switch (getParamType(par)) {
      case DOUBLE:
        setParamValue(par, (1 - t) * a.getParamValue(double_tag(), par) +
                               t * b.getParamValue(double_tag(), par));
        break;
      default:
        break;
      }
    }
  }

  invalidateIcon();
}

//===================================================================
//
// color style global list
//
//===================================================================

namespace {

class ColorStyleList {  // singleton
  ColorStyleList() {}

  struct Item {
    TColorStyle *m_style;
    bool m_isObsolete;
    //    Item() : m_style(0), m_isObsolete(false) { assert(0); }
    Item(TColorStyle *style, bool obsolete = false)
        : m_style(style), m_isObsolete(obsolete) {}
  };

  typedef std::map<int, Item> Table;
  Table m_table;

public:
  static ColorStyleList *instance() {
    static ColorStyleList *_instance = 0;
    if (!_instance) _instance = new ColorStyleList();
    return _instance;
  }

  int getStyleCount() { return int(m_table.size()); }

  void declare(TColorStyle *style) {
    int id = style->getTagId();
    if (m_table.find(id) != m_table.end()) {
      throw TException("Duplicate color style declaration. id = " +
                       std::to_string(id));
    }
    m_table.insert(std::make_pair(id, Item(style)));
    std::vector<int> ids;
    style->getObsoleteTagIds(ids);
    for (std::vector<int>::iterator it = ids.begin(); it != ids.end(); ++it) {
      if (m_table.find(*it) != m_table.end()) {
        throw TException(
            "Duplicate color style declaration for obsolete style. id = " +
            std::to_string(*it));
      }
      m_table.insert(std::make_pair(*it, Item(style->clone(), true)));
    }
  }

  TColorStyle *create(int id, bool &isObsolete) {
    Table::iterator it = m_table.find(id);
    if (it == m_table.end())
      throw TException("Unknown color style id; id = " + std::to_string(id));

    isObsolete = it->second.m_isObsolete;

    return it->second.m_style->clone();
  }

  TColorStyle *create(std::string brushIdName) {
    int index = brushIdName.find(':');
    std::string brushIdCategory =
        (index != -1) ? brushIdName.substr(0, index) : brushIdName;

    for (auto &table : m_table) {
      std::string name = table.second.m_style->getBrushIdName();
      int index        = name.find(':');
      if (index != -1 && brushIdCategory == name.substr(0, index)) {
        return table.second.m_style->clone(brushIdName);
      } else if (index == -1 && brushIdCategory == name) {
        return table.second.m_style->clone(brushIdName);
      }
    }
    return nullptr;
  }

  void getAllTags(std::vector<int> &tags) {
    tags.clear();
    tags.reserve(m_table.size());
    for (Table::iterator it = m_table.begin(); it != m_table.end(); ++it)
      if (!it->second.m_isObsolete) tags.push_back(it->first);
  }

  ~ColorStyleList() {
    Table::iterator it = m_table.begin();
    for (; it != m_table.end(); ++it) {
      delete it->second.m_style;
    }
  }

private:
  // not implemented
  ColorStyleList(const ColorStyleList &);
  ColorStyleList &operator=(const ColorStyleList &);
};

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

}  // namespace

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

void TColorStyle::declare(TColorStyle *style) {
  ColorStyleList::instance()->declare(style);
}

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

static double computeAverageThickness(const TStroke *s, double &minThickness,
                                      double &maxThickness) {
  int count = s->getControlPointCount();

  minThickness    = 1000;
  maxThickness    = -1;
  double resThick = 0;

  for (int i = 0; i < s->getControlPointCount(); i++) {
    double thick = s->getControlPoint(i).thick;
    if (i >= 2 && i < s->getControlPointCount() - 2) resThick += thick;

    if (thick < minThickness) minThickness = thick;
    if (thick > maxThickness) maxThickness = thick;
  }

  if (count < 6) return s->getControlPoint(count / 2 + 1).thick;
  return resThick / (s->getControlPointCount() - 4);
}

//-----------------------------------------------------------------------------
// Format: _123 |global name
// _123 = flag; optional (*)
// |global = global name; optional
// name = color name; mandatory (??)

// note: If name starts with a digit or by '_', another '_'
// is added.
// (*): In such case, the flag is mandatory.
void TColorStyle::save(TOutputStreamInterface &os) const {
  std::wstring name = getName();
  bool numberedName =
      !name.empty() && (('0' <= name[0] && name[0] <= '9') || name[0] == '_');

  if (m_flags > 0 || (name.length() == 1 && numberedName))
    os << ("_" + QString::number(m_flags)).toStdString();
  std::wstring gname    = getGlobalName();
  std::wstring origName = getOriginalName();

  if (gname != L"") {
    os << ::to_string(L"|" + gname);

    // save the original name from studio palette
    if (origName != L"") {
      // write two "@"s if the edited flag is ON
      os << ::to_string(((m_isEditedFromOriginal) ? L"@@" : L"@") + origName);
    }
  }

  if (numberedName) name.insert(0, L"_");

  os << ::to_string(name) << getTagId();
  saveData(os);
}

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

TColorStyle *TColorStyle::load(TInputStreamInterface &is) {
  std::string name;
  std::wstring gname;
  std::wstring origName;
  bool isEdited = false;

  is >> name;
  unsigned int flags = 0;
  if (name.length() == 2 && name[0] == '_' && '0' <= name[1] &&
      name[1] <= '9') {
    flags = QString::fromStdString(name.substr(1)).toUInt();
    is >> name;
  }
  if (name.length() > 0 && name[0] == '|') {
    gname = ::to_wstring(name.substr(1));
    is >> name;

    // If the style is copied from studio palette, original name is here
    if (name.length() > 0 && name[0] == '@') {
      // if there are two "@"s, then activate the edited flag
      if (name[1] == '@') {
        isEdited = true;
        origName = ::to_wstring(name.substr(2));
      } else {
        origName = ::to_wstring(name.substr(1));
      }
      is >> name;
    }
  }
  int id = 0;
  if (!name.empty() && '0' <= name[0] && name[0] <= '9') {
    id   = std::stoi(name);
    name = "color";
  } else {
    if (!name.empty() && name[0] == '_') name.erase(name.begin());
    is >> id;
  }
  bool isObsolete    = false;
  TColorStyle *style = ColorStyleList::instance()->create(id, isObsolete);
  assert(style);
  style->setFlags(flags);
  if (isObsolete)
    style->loadData(id, is);
  else
    style->loadData(is);
  style->setName(::to_wstring(name));
  style->setGlobalName(gname);
  style->setOriginalName(origName);
  style->setIsEditedFlag(isEdited);
  return style;
}

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

TColorStyle *TColorStyle::create(int tagId) {
  bool isObsolete = false;
  return ColorStyleList::instance()->create(tagId, isObsolete);
}

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

TColorStyle *TColorStyle::create(std::string brushIdName) {
  return ColorStyleList::instance()->create(brushIdName);
}

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

void TColorStyle::getAllTags(std::vector<int> &tags) {
  ColorStyleList::instance()->getAllTags(tags);
}

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

std::string TColorStyle::getBrushIdName() const {
  assert(false);
  return "InvalidStyle";
}

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

std::size_t TColorStyle::generateHash(std::string brushIdName) {
  std::hash<std::string> hasher;
  std::size_t v_hash = hasher(brushIdName);
  return v_hash;
}

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

std::size_t TColorStyle::getBrushIdHash() {
  if (m_hash) return m_hash;

  std::hash<std::string> hasher;
  std::string brushId = getBrushIdName();
  m_hash              = hasher(brushId);
  return m_hash;
}

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

std::string TColorStyle::getBrushIdNameClass(std::string brushIdName) {
  int index = brushIdName.find(':');
  if (index >= 0) return brushIdName.substr(0, index);
  return brushIdName;
}

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

std::string TColorStyle::getBrushIdNameParam(std::string brushIdName) {
  int index = brushIdName.find(':');
  if (index < 0) return "";
  return brushIdName.substr(index + 1);
}

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

TRectD TColorStyle::getStrokeBBox(const TStroke *stroke) const {
  return stroke->getBBox();
}