void WbIndexedFaceSet::rescale(const WbVector3 &v) {
  coord()->rescale(v);
}

#include "WbIndexedFaceSet.hpp"

#include "WbBoundingSphere.hpp"
#include "WbCoordinate.hpp"
#include "WbField.hpp"
#include "WbFieldChecker.hpp"
#include "WbMFInt.hpp"
#include "WbNodeUtilities.hpp"
#include "WbNormal.hpp"
#include "WbResizeManipulator.hpp"
#include "WbSFBool.hpp"
#include "WbSFDouble.hpp"
#include "WbSFNode.hpp"
#include "WbTextureCoordinate.hpp"
#include "WbTriangleMesh.hpp"

void WbIndexedFaceSet::init() {
  mCoord = findSFNode("coord");
  mNormal = findSFNode("normal");
  mTexCoord = findSFNode("texCoord");
  mCcw = findSFBool("ccw");
  mNormalPerVertex = findSFBool("normalPerVertex");
  mCoordIndex = findMFInt("coordIndex");
  mNormalIndex = findMFInt("normalIndex");
  mTexCoordIndex = findMFInt("texCoordIndex");
  mCreaseAngle = findSFDouble("creaseAngle");
}

WbIndexedFaceSet::WbIndexedFaceSet(WbTokenizer *tokenizer) : WbTriangleMeshGeometry("IndexedFaceSet", tokenizer) {
  init();
}

WbIndexedFaceSet::WbIndexedFaceSet(const WbIndexedFaceSet &other) : WbTriangleMeshGeometry(other) {
  init();
}

WbIndexedFaceSet::WbIndexedFaceSet(const WbNode &other) : WbTriangleMeshGeometry(other) {
  init();
}

WbIndexedFaceSet::~WbIndexedFaceSet() {
}

void WbIndexedFaceSet::preFinalize() {
  if (isPreFinalizedCalled())
    return;

  WbTriangleMeshGeometry::preFinalize();

  WbFieldChecker::resetDoubleIfNegative(this, mCreaseAngle, 0.0);
}

void WbIndexedFaceSet::postFinalize() {
  WbTriangleMeshGeometry::postFinalize();

  connect(mCoord, &WbSFNode::changed, this, &WbIndexedFaceSet::updateCoord);
  connect(mNormal, &WbSFNode::changed, this, &WbIndexedFaceSet::updateNormal);
  connect(mTexCoord, &WbSFNode::changed, this, &WbIndexedFaceSet::updateTexCoord);
  connect(mCcw, &WbSFBool::changed, this, &WbIndexedFaceSet::updateCcw);
  connect(mNormalPerVertex, &WbSFBool::changed, this, &WbIndexedFaceSet::updateNormalPerVertex);
  connect(mCoordIndex, &WbMFInt::changed, this, &WbIndexedFaceSet::updateCoordIndex);
  connect(mNormalIndex, &WbMFInt::changed, this, &WbIndexedFaceSet::updateNormalIndex);
  connect(mTexCoordIndex, &WbMFInt::changed, this, &WbIndexedFaceSet::updateTexCoordIndex);
  connect(mCreaseAngle, &WbSFDouble::changed, this, &WbIndexedFaceSet::updateCreaseAngle);

  if (coord())
    connect(coord(), &WbCoordinate::fieldChanged, this, &WbIndexedFaceSet::updateCoord, Qt::UniqueConnection);

  if (normal())
    connect(normal(), &WbNormal::fieldChanged, this, &WbIndexedFaceSet::updateNormal, Qt::UniqueConnection);

  if (texCoord())
    connect(texCoord(), &WbTextureCoordinate::fieldChanged, this, &WbIndexedFaceSet::updateTexCoord, Qt::UniqueConnection);
}

void WbIndexedFaceSet::reset(const QString &id) {
  WbTriangleMeshGeometry::reset(id);

  WbNode *const coord = mCoord->value();
  if (coord)
    coord->reset(id);
  WbNode *const normal = mNormal->value();
  if (normal)
    normal->reset(id);
  WbNode *const texCoord = mTexCoord->value();
  if (texCoord)
    texCoord->reset(id);
}

void WbIndexedFaceSet::updateTriangleMesh(bool issueWarnings) {
  mTriangleMeshError =
    mTriangleMesh->init(coord() ? &(coord()->point()) : NULL, mCoordIndex, normal() ? &(normal()->vector()) : NULL,
                        mNormalIndex, texCoord() ? &(texCoord()->point()) : NULL, mTexCoordIndex, mCreaseAngle->value(),
                        mCcw->value(), mNormalPerVertex->value());

  if (issueWarnings) {
    foreach (QString warning, mTriangleMesh->warnings())
      parsingWarn(warning);

    if (!mTriangleMeshError.isEmpty())
      parsingWarn(tr("Cannot create IndexedFaceSet because: \"%1\".").arg(mTriangleMeshError));
  }
}

uint64_t WbIndexedFaceSet::computeHash() const {
  uint64_t hash = 0;

  if (coord() && coord()->pointSize()) {
    const double *startCoord = coord()->point().item(0).ptr();
    int size = 3 * coord()->pointSize();
    hash ^= WbTriangleMeshCache::sipHash13x(startCoord, size);
  }

  if (normal() && normal()->vectorSize()) {
    const double *startNormal = normal()->vector().item(0).ptr();
    int size = 2 * normal()->vectorSize();
    hash ^= WbTriangleMeshCache::sipHash13x(startNormal, size);
  }

  if (texCoord() && texCoord()->pointSize()) {
    const double *startTexCoord = texCoord()->point().item(0).ptr();
    int size = 2 * texCoord()->pointSize();
    hash ^= WbTriangleMeshCache::sipHash13x(startTexCoord, size);
  }

  if (coordIndex() && coordIndex()->size()) {
    const int *startCoordIndex = &(coordIndex()->item(0));
    hash ^= WbTriangleMeshCache::sipHash13x(startCoordIndex, coordIndex()->size());
  }

  if (normalIndex() && normalIndex()->size()) {
    const int *startNormalIndex = &(normalIndex()->item(0));
    hash ^= WbTriangleMeshCache::sipHash13x(startNormalIndex, normalIndex()->size());
  }

  if (texCoordIndex() && texCoordIndex()->size()) {
    const int *startTexCoordIndex = &(texCoordIndex()->item(0));
    hash ^= WbTriangleMeshCache::sipHash13x(startTexCoordIndex, texCoordIndex()->size());
  }

  hash ^= WbTriangleMeshCache::sipHash13x(creaseAngle()->valuePointer(), 1);
  hash ^= WbTriangleMeshCache::sipHash13x(ccw()->valuePointer(), 1);
  hash ^= WbTriangleMeshCache::sipHash13x(normalPerVertex()->valuePointer(), 1);

  return hash;
}

WbCoordinate *WbIndexedFaceSet::coord() const {
  return static_cast<WbCoordinate *>(mCoord->value());
}

WbNormal *WbIndexedFaceSet::normal() const {
  return static_cast<WbNormal *>(mNormal->value());
}

WbTextureCoordinate *WbIndexedFaceSet::texCoord() const {
  return static_cast<WbTextureCoordinate *>(mTexCoord->value());
}

void WbIndexedFaceSet::createResizeManipulator() {
  mResizeManipulator =
    new WbRegularResizeManipulator(uniqueId(), WbWrenAbstractResizeManipulator::ResizeConstraint::NO_CONSTRAINT);
}

bool WbIndexedFaceSet::areSizeFieldsVisibleAndNotRegenerator() const {
  const WbField *const coordinates = findField("coord", true);
  return WbNodeUtilities::isVisible(coordinates) && !WbNodeUtilities::isTemplateRegeneratorField(coordinates);
}

void WbIndexedFaceSet::attachResizeManipulator() {
  if (coord())
    WbTriangleMeshGeometry::attachResizeManipulator();
}

void WbIndexedFaceSet::updateCoord() {
  if (coord())
    connect(coord(), &WbCoordinate::fieldChanged, this, &WbIndexedFaceSet::updateCoord, Qt::UniqueConnection);

  buildWrenMesh(true);

  if (isAValidBoundingObject())
    applyToOdeData();

  if (mBoundingSphere && !isInBoundingObject())
    mBoundingSphere->setOwnerSizeChanged();

  if (resizeManipulator() && resizeManipulator()->isAttached())
    setResizeManipulatorDimensions();  // Must be called after updateTriangleMesh()

  emit changed();
}

void WbIndexedFaceSet::updateNormal() {
  if (normal()) {
    connect(normal(), &WbNormal::fieldChanged, this, &WbIndexedFaceSet::updateNormal, Qt::UniqueConnection);
    for (int i = 0; i < normal()->vector().size(); ++i) {
      if (normal()->vector(i).isNull()) {
        normal()->setVector(i, WbVector3(0.0, 1.0, 0.0));
        parsingWarn(tr("Normal values can't be null."));
      }
    }
  }

  buildWrenMesh(true);

  emit changed();
}

void WbIndexedFaceSet::updateTexCoord() {
  if (texCoord())
    connect(texCoord(), &WbTextureCoordinate::fieldChanged, this, &WbIndexedFaceSet::updateTexCoord, Qt::UniqueConnection);

  buildWrenMesh(true);

  emit changed();
}

void WbIndexedFaceSet::updateCcw() {
  buildWrenMesh(true);

  emit changed();
}

void WbIndexedFaceSet::updateNormalPerVertex() {
  buildWrenMesh(true);

  emit changed();
}

void WbIndexedFaceSet::updateCoordIndex() {
  buildWrenMesh(true);

  if (isAValidBoundingObject())
    applyToOdeData();

  if (mBoundingSphere && !isInBoundingObject())
    mBoundingSphere->setOwnerSizeChanged();

  if (resizeManipulator() && resizeManipulator()->isAttached())
    setResizeManipulatorDimensions();  // Must be called after updateTriangleMesh()

  emit changed();
}

void WbIndexedFaceSet::updateNormalIndex() {
  buildWrenMesh(true);

  emit changed();
}

void WbIndexedFaceSet::updateTexCoordIndex() {
  buildWrenMesh(true);

  emit changed();
}

void WbIndexedFaceSet::updateCreaseAngle() {
  if (WbFieldChecker::resetDoubleIfNegative(this, mCreaseAngle, 0.0))
    return;

  buildWrenMesh(true);

  emit changed();
}

//  WREN related methods for resizing by pulling handles   //

void WbIndexedFaceSet::rescaleAndTranslate(int coordinate, double scale, double translation) {
  coord()->rescaleAndTranslate(coordinate, scale, translation);
}

void WbIndexedFaceSet::rescaleAndTranslate(double factor, const WbVector3 &t) {
  coord()->rescaleAndTranslate(WbVector3(factor, factor, factor), t);
}

void WbIndexedFaceSet::rescaleAndTranslate(const WbVector3 &scale, const WbVector3 &translation) {
  coord()->rescaleAndTranslate(scale, translation);
}

void WbIndexedFaceSet::translate(const WbVector3 &v) {
  coord()->translate(v);
}


AntiLogRenderer::AntiLogRenderer(ConstLogOpDataRcPtr & log, float log2base)
    : LogOpCPU(log)
    , m_log2_base(log2base)
{
    LogOpCPU::updateData(log);
}

// Copyright Contributors to the OpenColorIO Project.

#include <algorithm>
#include <cstring>
#include <cmath>
#ifndef USE_SSE
#include <tuple>
#endif

#include <OpenColorIO/OpenColorIO.h>

#include "BitDepthUtils.h"
#include "MathUtils.h"
#include "ops/log/LogOpCPU.h"
#include "ops/log/LogUtils.h"
#include "ops/OpTools.h"
#include "Platform.h"
#include "SSE.h"

namespace OCIO_NAMESPACE
{
class LogOpCPU : public OpCPU
{
public:
    LogOpCPU() = delete;
    LogOpCPU(const LogOpCPU &) = delete;

    explicit LogOpCPU(ConstLogOpDataRcPtr & log);

protected:
    // Update renderer parameters.
    virtual void updateData(ConstLogOpDataRcPtr & log);
};

// Base class for LogToLin and LinToLog renderers.
class L2LBaseRenderer : public LogOpCPU
{
public:
    explicit L2LBaseRenderer(ConstLogOpDataRcPtr & log);

protected:
    void updateData(ConstLogOpDataRcPtr & log) override;

protected:
    float m_base = 2.0f;
    LogOpData::Params m_paramsR;
    LogOpData::Params m_paramsG;
    LogOpData::Params m_paramsB;
};

// Renderer for LogToLin operations.
class Log2LinRenderer : public L2LBaseRenderer
{
public:
    explicit Log2LinRenderer(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;

protected:
    void updateData(ConstLogOpDataRcPtr & log) override;

    float m_kinv[3];
    float m_minuskb[3];
    float m_minusb[3];
    float m_minv[3];
};

#ifdef USE_SSE
class Log2LinRendererSSE : public Log2LinRenderer
{
public:
    explicit Log2LinRendererSSE(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;
};
#endif

// Renderer for Lin2Log operations.
class Lin2LogRenderer : public L2LBaseRenderer
{
public:
    explicit Lin2LogRenderer(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;

protected:
    void updateData(ConstLogOpDataRcPtr & log) override;

    float m_m[3];
    float m_b[3];
    float m_klog[3];
    float m_kb[3];
};

#ifdef USE_SSE
class Lin2LogRendererSSE : public Lin2LogRenderer
{
public:
    explicit Lin2LogRendererSSE(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;
};
#endif

class CameraL2LBaseRenderer : public L2LBaseRenderer
{
public:
    explicit CameraL2LBaseRenderer(ConstLogOpDataRcPtr & log);

protected:
    void updateData(ConstLogOpDataRcPtr & log) override;

    float m_logSideBreak[3];
    float m_linearSlope[3];
    float m_linearOffset[3];
    float m_log2_base;
};

// Renderer for CameraLogToLin operations.
class CameraLog2LinRenderer : public CameraL2LBaseRenderer
{
public:
    explicit CameraLog2LinRenderer(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;

protected:
    void updateData(ConstLogOpDataRcPtr & log) override;

    float m_kinv[3];
    float m_minuskb[3];
    float m_minusb[3];
    float m_minv[3];
    float m_linsinv[3];
    float m_minuslino[3];
};

#ifdef USE_SSE
class CameraLog2LinRendererSSE : public CameraLog2LinRenderer
{
public:
    explicit CameraLog2LinRendererSSE(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;
};
#endif

// Renderer for CameraLin2Log operations.
class CameraLin2LogRenderer : public CameraL2LBaseRenderer
{
public:
    explicit CameraLin2LogRenderer(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;

protected:
    void updateData(ConstLogOpDataRcPtr & log) override;

    float m_m[3];
    float m_b[3];
    float m_klog[3];
    float m_kb[3];
    float m_linb[3];
};

#ifdef USE_SSE
class CameraLin2LogRendererSSE : public CameraLin2LogRenderer
{
public:
    explicit CameraLin2LogRendererSSE(ConstLogOpDataRcPtr & log);

    void apply(const void * inImg, void * outImg, long numPixels) const override;
};
#endif

// Renderer for Log10 and Log2 operations.
class LogRenderer : public LogOpCPU
{
public:
    explicit LogRenderer(ConstLogOpDataRcPtr & log, float logScale);

    void apply(const void * inImg, void * outImg, long numPixels) const override;

protected:
    float m_logScale;
};

#ifdef USE_SSE
class LogRendererSSE : public LogRenderer
{
public:
    explicit LogRendererSSE(ConstLogOpDataRcPtr & log, float logScale);

    void apply(const void * inImg, void * outImg, long numPixels) const override;
};
#endif

// Renderer for AntiLog10 and AntiLog2 operations.
class AntiLogRenderer : public LogOpCPU
{
public:
    explicit AntiLogRenderer(ConstLogOpDataRcPtr & log, float log2base);

    void apply(const void * inImg, void * outImg, long numPixels) const override;

protected:
    float m_log2_base;
};

#ifdef USE_SSE
class AntiLogRendererSSE : public AntiLogRenderer
{
public:
    explicit AntiLogRendererSSE(ConstLogOpDataRcPtr & log, float log2base);

    void apply(const void * inImg, void * outImg, long numPixels) const override;
};
#endif

static constexpr float LOG2_10 = ((float) 3.3219280948873623478703194294894);
static constexpr float LOG10_2 = ((float) 0.3010299956639811952137388947245);

ConstOpCPURcPtr GetLogRenderer(ConstLogOpDataRcPtr & log, bool fastExp)
{
#ifndef USE_SSE
    std::ignore = fastExp;
#endif

    const TransformDirection dir = log->getDirection();
    if (log->isLog2())
    {
        switch (dir)
        {
        case TRANSFORM_DIR_FORWARD:
#ifdef USE_SSE
            if (fastExp) return std::make_shared<LogRendererSSE>(log, 1.0f);
            else
#endif
                return std::make_shared<LogRenderer>(log, 1.0f);
            break;
        case TRANSFORM_DIR_INVERSE:
#ifdef USE_SSE
            if (fastExp) return std::make_shared<AntiLogRendererSSE>(log, 1.0f);
            else
#endif
                return std::make_shared<AntiLogRenderer>(log, 1.0f);
            break;
        }
    }
    else if (log->isLog10())
    {
        switch (dir)
        {
        case TRANSFORM_DIR_FORWARD:
#ifdef USE_SSE
            if (fastExp) return std::make_shared<LogRendererSSE>(log, LOG10_2);
            else
#endif
                return std::make_shared<LogRenderer>(log, LOG10_2);
            break;
        case TRANSFORM_DIR_INVERSE:
#ifdef USE_SSE
            if (fastExp) return std::make_shared<AntiLogRendererSSE>(log, LOG2_10);
            else
#endif
                return std::make_shared<AntiLogRenderer>(log, LOG2_10);
            break;
        }
    }
    else
    {
        if (log->isCamera())
        {
            switch (dir)
            {
            case TRANSFORM_DIR_FORWARD:
#ifdef USE_SSE
                if (fastExp) return std::make_shared<CameraLin2LogRendererSSE>(log);
                else
#endif
                    return std::make_shared<CameraLin2LogRenderer>(log);
                break;
            case TRANSFORM_DIR_INVERSE:
#ifdef USE_SSE
                if (fastExp) return std::make_shared<CameraLog2LinRendererSSE>(log);
                else
#endif
                    return std::make_shared<CameraLog2LinRenderer>(log);
                break;
            }
        }
        else
        {
            switch (dir)
            {
            case TRANSFORM_DIR_FORWARD:
#ifdef USE_SSE
                if (fastExp) return std::make_shared<Lin2LogRendererSSE>(log);
                else
#endif
                    return std::make_shared<Lin2LogRenderer>(log);
                break;
            case TRANSFORM_DIR_INVERSE:
#ifdef USE_SSE
                if (fastExp) return std::make_shared<Log2LinRendererSSE>(log);
                else
#endif
                    return std::make_shared<Log2LinRenderer>(log);
                break;
            }
        }
    }
    throw Exception("Illegal Log direction.");
}

LogOpCPU::LogOpCPU(ConstLogOpDataRcPtr & log)
    : OpCPU()
{
}

void LogOpCPU::updateData(ConstLogOpDataRcPtr & log)
{
}

L2LBaseRenderer::L2LBaseRenderer(ConstLogOpDataRcPtr & log)
    : LogOpCPU(log)
{
}

void L2LBaseRenderer::updateData(ConstLogOpDataRcPtr & log)
{
    LogOpCPU::updateData(log);

    m_base = (float)log->getBase();
    m_paramsR = log->getRedParams();
    m_paramsG = log->getGreenParams();
    m_paramsB = log->getBlueParams();
}

LogRenderer::LogRenderer(ConstLogOpDataRcPtr & log, float logScale)
    : LogOpCPU(log)
    , m_logScale(logScale)
{
    LogOpCPU::updateData(log);
}

inline void ApplyScale(float * pix, const float scale)
{
    pix[0] = pix[0] * scale;
    pix[1] = pix[1] * scale;
    pix[2] = pix[2] * scale;
}

inline void ApplyScale(float * pix, const float * scale)
{
    pix[0] = pix[0] * scale[0];
    pix[1] = pix[1] * scale[1];
    pix[2] = pix[2] * scale[2];
}

inline void ApplyAdd(float * pix, const float * add)
{
    pix[0] = pix[0] + add[0];
    pix[1] = pix[1] + add[1];
    pix[2] = pix[2] + add[2];
}

inline void ApplyMax(float * pix, float minValue)
{
    pix[0] = std::max(minValue, pix[0]);
    pix[1] = std::max(minValue, pix[1]);
    pix[2] = std::max(minValue, pix[2]);
}

inline void ApplyLog2(float * pix)
{
    pix[0] = log2(pix[0]);
    pix[1] = log2(pix[1]);
    pix[2] = log2(pix[2]);
}

inline void ApplyExp2(float * pix)
{
    pix[0] = exp2(pix[0]);
    pix[1] = exp2(pix[1]);
    pix[2] = exp2(pix[2]);
}

void LogRenderer::apply(const void * inImg, void * outImg, long numPixels) const
{
    static constexpr float minValue = std::numeric_limits<float>::min();

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        const float alphares = in[3];

        // NB: 'in' and 'out' could be pointers to the same memory buffer.
        std::memcpy(out, in, 4 * sizeof(float));

        ApplyMax(out, minValue);
        ApplyLog2(out);
        ApplyScale(out, m_logScale);

        out[3] = alphares;

        in  += 4;
        out += 4;
    }
}

#ifdef USE_SSE
LogRendererSSE::LogRendererSSE(ConstLogOpDataRcPtr & log, float logScale)
    : LogRenderer(log, logScale)
{
}
void LogRendererSSE::apply(const void * inImg, void * outImg, long numPixels) const
{
    static constexpr float minValue = std::numeric_limits<float>::min();

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    const __m128 mm_minValue = _mm_set1_ps(minValue);
    const __m128 mm_logScale = _mm_set1_ps(m_logScale);

    __m128 mm_pixel;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        mm_pixel = _mm_set_ps(0.0f, in[2], in[1], in[0]);
        mm_pixel = _mm_max_ps(mm_pixel, mm_minValue);
        mm_pixel = sseLog2(mm_pixel);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_logScale);

        const float alphares = in[3];

        _mm_storeu_ps(out, mm_pixel);
        out[3] = alphares;

        in  += 4;
        out += 4;
    }
}
#endif

// Renderer for AntiLog10 and AntiLog2 operations

void AntiLogRenderer::apply(const void * inImg, void * outImg, long numPixels) const
{
    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        const float alphares = in[3];

        // NB: 'in' and 'out' could be pointers to the same memory buffer.
        std::memcpy(out, in, 4 * sizeof(float));

        ApplyScale(out, m_log2_base);
        ApplyExp2(out);

        out[3] = alphares;

        in  += 4;
        out += 4;
    }
}

#ifdef USE_SSE
AntiLogRendererSSE::AntiLogRendererSSE(ConstLogOpDataRcPtr & log, float log2base)
    : AntiLogRenderer(log, log2base)
{
}

void AntiLogRendererSSE::apply(const void * inImg, void * outImg, long numPixels) const
{
    // This computation is decomposed into:
    //   so that the constant factor log2(base) can be moved outside the loop.

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    const __m128 mm_log2_base = _mm_set1_ps(m_log2_base);

    __m128 mm_pixel;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        mm_pixel = _mm_set_ps(0.0f, in[2], in[1], in[0]);
        mm_pixel = sseExp2(_mm_mul_ps(mm_pixel, mm_log2_base));

        const float alphares = in[3];

        _mm_storeu_ps(out, mm_pixel);
        out[3] = alphares;

        in  += 4;
        out += 4;
    }
}
#endif

// Renderer for LogToLin operations
Log2LinRenderer::Log2LinRenderer(ConstLogOpDataRcPtr & log)
    : L2LBaseRenderer(log)
{
    updateData(log);
}

void Log2LinRenderer::updateData(ConstLogOpDataRcPtr & log)
{
    L2LBaseRenderer::updateData(log);

    m_kinv[0] = log2f(m_base) / (float)m_paramsR[LOG_SIDE_SLOPE];
    m_kinv[1] = log2f(m_base) / (float)m_paramsG[LOG_SIDE_SLOPE];
    m_kinv[2] = log2f(m_base) / (float)m_paramsB[LOG_SIDE_SLOPE];
    m_minuskb[0] = -(float)m_paramsR[LOG_SIDE_OFFSET];
    m_minuskb[1] = -(float)m_paramsG[LOG_SIDE_OFFSET];
    m_minuskb[2] = -(float)m_paramsB[LOG_SIDE_OFFSET];
    m_minusb[0] = -(float)m_paramsR[LIN_SIDE_OFFSET];
    m_minusb[1] = -(float)m_paramsG[LIN_SIDE_OFFSET];
    m_minusb[2] = -(float)m_paramsB[LIN_SIDE_OFFSET];
    m_minv[0] = 1.0f / (float)m_paramsR[LIN_SIDE_SLOPE];
    m_minv[1] = 1.0f / (float)m_paramsG[LIN_SIDE_SLOPE];
    m_minv[2] = 1.0f / (float)m_paramsB[LIN_SIDE_SLOPE];
}

void Log2LinRenderer::apply(const void * inImg, void * outImg, long numPixels) const
{
    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        const float alphares = in[3];

        // NB: 'in' and 'out' could be pointers to the same memory buffer.
        std::memcpy(out, in, 4 * sizeof(float));

        ApplyAdd(out, m_minuskb);
        ApplyScale(out, m_kinv);
        ApplyExp2(out);
        ApplyAdd(out, m_minusb);
        ApplyScale(out, m_minv);

        out[3] = alphares;

        out += 4;
        in  += 4;
    }
}

#ifdef USE_SSE
Log2LinRendererSSE::Log2LinRendererSSE(ConstLogOpDataRcPtr & log)
    : Log2LinRenderer(log)
{

}

void Log2LinRendererSSE::apply(const void * inImg, void * outImg, long numPixels) const
{
    // The power computation is decomposed into:
    //   so that the constant factor log2(base) can be moved outside the loop.

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    const __m128 mm_kinv = _mm_set_ps(0.0f, m_kinv[2], m_kinv[1], m_kinv[0]);
    const __m128 mm_minuskb = _mm_set_ps(0.0f, m_minuskb[2], m_minuskb[1], m_minuskb[0]);
    const __m128 mm_minusb = _mm_set_ps(0.0f, m_minusb[2], m_minusb[1], m_minusb[0]);
    const __m128 mm_minv = _mm_set_ps(0.0f, m_minv[2], m_minv[1], m_minv[0]);

    __m128 mm_pixel;

    for (long idx = 0; idx < numPixels; ++idx)
    {
        mm_pixel = _mm_set_ps(0.0f, in[2], in[1], in[0]);
        mm_pixel = _mm_add_ps(mm_pixel, mm_minuskb);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_kinv);
        mm_pixel = sseExp2(mm_pixel);
        mm_pixel = _mm_add_ps(mm_pixel, mm_minusb);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_minv);

        const float alphares = in[3];

        _mm_storeu_ps(out, mm_pixel);
        out[3] = alphares;

        out += 4;
        in  += 4;
    }
}
#endif

// Renderer for Lin2Log operations
Lin2LogRenderer::Lin2LogRenderer(ConstLogOpDataRcPtr & log)
    : L2LBaseRenderer(log)
{
    updateData(log);
}

void Lin2LogRenderer::updateData(ConstLogOpDataRcPtr & log)
{
    L2LBaseRenderer::updateData(log);

    m_m[0] = (float)m_paramsR[LIN_SIDE_SLOPE];
    m_m[1] = (float)m_paramsG[LIN_SIDE_SLOPE];
    m_m[2] = (float)m_paramsB[LIN_SIDE_SLOPE];
    m_b[0] = (float)m_paramsR[LIN_SIDE_OFFSET];
    m_b[1] = (float)m_paramsG[LIN_SIDE_OFFSET];
    m_b[2] = (float)m_paramsB[LIN_SIDE_OFFSET];
    m_klog[0] = (float)(m_paramsR[LOG_SIDE_SLOPE] / log2(m_base));
    m_klog[1] = (float)(m_paramsG[LOG_SIDE_SLOPE] / log2(m_base));
    m_klog[2] = (float)(m_paramsB[LOG_SIDE_SLOPE] / log2(m_base));
    m_kb[0] = (float)m_paramsR[LOG_SIDE_OFFSET];
    m_kb[1] = (float)m_paramsG[LOG_SIDE_OFFSET];
    m_kb[2] = (float)m_paramsB[LOG_SIDE_OFFSET];
}

void Lin2LogRenderer::apply(const void * inImg, void * outImg, long numPixels) const
{
    static constexpr float minValue = std::numeric_limits<float>::min();

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        const float alphares = in[3];

        // NB: 'in' and 'out' could be pointers to the same memory buffer.
        std::memcpy(out, in, 4 * sizeof(float));

        ApplyScale(out, m_m);
        ApplyAdd(out, m_b);
        ApplyMax(out, minValue);
        ApplyLog2(out);
        ApplyScale(out, m_klog);
        ApplyAdd(out, m_kb);

        out[3] = alphares;

        out += 4;
        in  += 4;
    }
}

#ifdef USE_SSE
Lin2LogRendererSSE::Lin2LogRendererSSE(ConstLogOpDataRcPtr & log)
    : Lin2LogRenderer(log)
{
}

void Lin2LogRendererSSE::apply(const void * inImg, void * outImg, long numPixels) const
{
    static constexpr float minValue = std::numeric_limits<float>::min();

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    const __m128 mm_minValue = _mm_set1_ps(minValue);

    const __m128 mm_m = _mm_set_ps(0.0f, m_m[2], m_m[1], m_m[0]);
    const __m128 mm_b = _mm_set_ps(0.0f, m_b[2], m_b[1], m_b[0]);
    const __m128 mm_klog = _mm_set_ps(0.0f, m_klog[2], m_klog[1], m_klog[0]);
    const __m128 mm_kb = _mm_set_ps(0.0f, m_kb[2], m_kb[1], m_kb[0]);

    __m128 mm_pixel;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        mm_pixel = _mm_set_ps(0.0f, in[2], in[1], in[0]);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_m);
        mm_pixel = _mm_add_ps(mm_pixel, mm_b);
        mm_pixel = _mm_max_ps(mm_pixel, mm_minValue);
        mm_pixel = sseLog2(mm_pixel);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_klog);
        mm_pixel = _mm_add_ps(mm_pixel, mm_kb);

        const float alphares = in[3];

        _mm_storeu_ps(out, mm_pixel);
        out[3] = alphares;

        out += 4;
        in  += 4;
    }
}
#endif

CameraL2LBaseRenderer::CameraL2LBaseRenderer(ConstLogOpDataRcPtr & log)
    : L2LBaseRenderer(log)
{
}

void CameraL2LBaseRenderer::updateData(ConstLogOpDataRcPtr & log)
{
    L2LBaseRenderer::updateData(log);
    m_linearSlope[0] = LogUtil::GetLinearSlope(m_paramsR, m_base);
    m_linearSlope[1] = LogUtil::GetLinearSlope(m_paramsG, m_base);
    m_linearSlope[2] = LogUtil::GetLinearSlope(m_paramsB, m_base);
    m_logSideBreak[0] = LogUtil::GetLogSideBreak(m_paramsR, m_base);
    m_logSideBreak[1] = LogUtil::GetLogSideBreak(m_paramsG, m_base);
    m_logSideBreak[2] = LogUtil::GetLogSideBreak(m_paramsB, m_base);
    m_linearOffset[0] = LogUtil::GetLinearOffset(m_paramsR, m_linearSlope[0], m_logSideBreak[0]);
    m_linearOffset[1] = LogUtil::GetLinearOffset(m_paramsG, m_linearSlope[1], m_logSideBreak[1]);
    m_linearOffset[2] = LogUtil::GetLinearOffset(m_paramsB, m_linearSlope[2], m_logSideBreak[2]);

    m_log2_base = log2((float)m_base);
}

CameraLog2LinRenderer::CameraLog2LinRenderer(ConstLogOpDataRcPtr & log)
    : CameraL2LBaseRenderer(log)
{
    updateData(log);
}

void CameraLog2LinRenderer::updateData(ConstLogOpDataRcPtr & log)
{
    CameraL2LBaseRenderer::updateData(log);

    m_kinv[0] = m_log2_base / (float)m_paramsR[LOG_SIDE_SLOPE];
    m_kinv[1] = m_log2_base / (float)m_paramsG[LOG_SIDE_SLOPE];
    m_kinv[2] = m_log2_base / (float)m_paramsB[LOG_SIDE_SLOPE];
    m_minuskb[0] = -(float)m_paramsR[LOG_SIDE_OFFSET];
    m_minuskb[1] = -(float)m_paramsG[LOG_SIDE_OFFSET];
    m_minuskb[2] = -(float)m_paramsB[LOG_SIDE_OFFSET];
    m_minusb[0] = -(float)m_paramsR[LIN_SIDE_OFFSET];
    m_minusb[1] = -(float)m_paramsG[LIN_SIDE_OFFSET];
    m_minusb[2] = -(float)m_paramsB[LIN_SIDE_OFFSET];
    m_minv[0] = 1.0f / (float)m_paramsR[LIN_SIDE_SLOPE];
    m_minv[1] = 1.0f / (float)m_paramsG[LIN_SIDE_SLOPE];
    m_minv[2] = 1.0f / (float)m_paramsB[LIN_SIDE_SLOPE];
    m_linsinv[0] = 1.0f / m_linearSlope[0];
    m_linsinv[1] = 1.0f / m_linearSlope[1];
    m_linsinv[2] = 1.0f / m_linearSlope[2];
    m_minuslino[0] = -m_linearOffset[0];
    m_minuslino[1] = -m_linearOffset[1];
    m_minuslino[2] = -m_linearOffset[2];
}

void CameraLog2LinRenderer::apply(const void * inImg, void * outImg, long numPixels) const
{
    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    for (long idx = 0; idx < numPixels; ++idx)
    {
        const float alphares = in[3];

        for (int i = 0; i < 3; ++i)
        {
            if (in[i] < m_logSideBreak[i])
            {
                out[i] = m_linsinv[i] * (in[i] + m_minuslino[i]);
            }
            else
            {
                out[i] = (in[i] + m_minuskb[i]) * m_kinv[i];
                out[i] = exp2(out[i]);
                out[i] = (out[i] + m_minusb[i]) * m_minv[i];
            }
        }

        out[3] = alphares;

        out += 4;
        in += 4;
    }
}

#ifdef USE_SSE
CameraLog2LinRendererSSE::CameraLog2LinRendererSSE(ConstLogOpDataRcPtr & log)
    : CameraLog2LinRenderer(log)
{
}

void CameraLog2LinRendererSSE::apply(const void * inImg, void * outImg, long numPixels) const
{

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    const __m128 mm_kinv = _mm_set_ps(0.0f, m_kinv[2], m_kinv[1], m_kinv[0]);
    const __m128 mm_minuskb = _mm_set_ps(0.0f, m_minuskb[2], m_minuskb[1], m_minuskb[0]);
    const __m128 mm_minusb = _mm_set_ps(0.0f, m_minusb[2], m_minusb[1], m_minusb[0]);
    const __m128 mm_minv = _mm_set_ps(0.0f, m_minv[2], m_minv[1], m_minv[0]);
    const __m128 breakPnt = _mm_set_ps(0.f, m_logSideBreak[2], m_logSideBreak[1], m_logSideBreak[0]);
    const __m128 mm_linoinv = _mm_set_ps(0.0f, m_minuslino[2], m_minuslino[1], m_minuslino[0]);
    const __m128 mm_linsinv = _mm_set_ps(0.0f, m_linsinv[2], m_linsinv[1], m_linsinv[0]);

    __m128 mm_pixel;
    __m128 mm_pixel_lin;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        mm_pixel = _mm_set_ps(0.0f, in[2], in[1], in[0]);
        __m128 flag = _mm_cmpgt_ps(mm_pixel, breakPnt);

        mm_pixel_lin = _mm_add_ps(mm_pixel, mm_linoinv);
        mm_pixel_lin = _mm_mul_ps(mm_pixel_lin, mm_linsinv);

        mm_pixel = _mm_add_ps(mm_pixel, mm_minuskb);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_kinv);
        mm_pixel = sseExp2(mm_pixel);
        mm_pixel = _mm_add_ps(mm_pixel, mm_minusb);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_minv);

        mm_pixel = _mm_or_ps(_mm_and_ps(flag, mm_pixel),
                             _mm_andnot_ps(flag, mm_pixel_lin));

        const float alphares = in[3];

        _mm_storeu_ps(out, mm_pixel);
        out[3] = alphares;

        out += 4;
        in += 4;
    }
}
#endif

CameraLin2LogRenderer::CameraLin2LogRenderer(ConstLogOpDataRcPtr & log)
    : CameraL2LBaseRenderer(log)
{
    updateData(log);
}

void CameraLin2LogRenderer::updateData(ConstLogOpDataRcPtr & log)
{
    CameraL2LBaseRenderer::updateData(log);

    m_m[0] = (float)m_paramsR[LIN_SIDE_SLOPE];
    m_m[1] = (float)m_paramsG[LIN_SIDE_SLOPE];
    m_m[2] = (float)m_paramsB[LIN_SIDE_SLOPE];
    m_b[0] = (float)m_paramsR[LIN_SIDE_OFFSET];
    m_b[1] = (float)m_paramsG[LIN_SIDE_OFFSET];
    m_b[2] = (float)m_paramsB[LIN_SIDE_OFFSET];
    m_klog[0] = (float)(m_paramsR[LOG_SIDE_SLOPE] / m_log2_base);
    m_klog[1] = (float)(m_paramsG[LOG_SIDE_SLOPE] / m_log2_base);
    m_klog[2] = (float)(m_paramsB[LOG_SIDE_SLOPE] / m_log2_base);
    m_kb[0] = (float)m_paramsR[LOG_SIDE_OFFSET];
    m_kb[1] = (float)m_paramsG[LOG_SIDE_OFFSET];
    m_kb[2] = (float)m_paramsB[LOG_SIDE_OFFSET];
    m_linb[0] = (float)m_paramsR[LIN_SIDE_BREAK];
    m_linb[1] = (float)m_paramsG[LIN_SIDE_BREAK];
    m_linb[2] = (float)m_paramsB[LIN_SIDE_BREAK];
}

void CameraLin2LogRenderer::apply(const void * inImg, void * outImg, long numPixels) const
{
    static constexpr float minValue = std::numeric_limits<float>::min();

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        const float alphares = in[3];

        for (int i = 0; i < 3; ++i)
        {
            if (in[i] < m_linb[i])
            {
                out[i] = m_linearSlope[i] * in[i] + m_linearOffset[i];
            }
            else
            {
                out[i] = in[i] * m_m[i] + m_b[i];
                out[i] = std::max(minValue, out[i]);
                out[i] = log2(out[i]);
                out[i] = out[i] * m_klog[i] + m_kb[i];
            }
        }

        out[3] = alphares;

        out += 4;
        in += 4;
    }
}

#ifdef USE_SSE
CameraLin2LogRendererSSE::CameraLin2LogRendererSSE(ConstLogOpDataRcPtr & log)
    : CameraLin2LogRenderer(log)
{
}

void CameraLin2LogRendererSSE::apply(const void * inImg, void * outImg, long numPixels) const
{
    //  out = linearSlope * in + linearOffset
    static constexpr float minValue = std::numeric_limits<float>::min();

    const float * in = (const float *)inImg;
    float * out = (float *)outImg;

    const __m128 mm_minValue = _mm_set1_ps(minValue);

    const __m128 mm_m = _mm_set_ps(0.0f, m_m[2], m_m[1], m_m[0]);
    const __m128 mm_b = _mm_set_ps(0.0f, m_b[2], m_b[1], m_b[0]);
    const __m128 mm_klog = _mm_set_ps(0.0f, m_klog[2], m_klog[1], m_klog[0]);
    const __m128 mm_kb = _mm_set_ps(0.0f, m_kb[2], m_kb[1], m_kb[0]);
    const __m128 mm_lins = _mm_set_ps(0.0f, m_linearSlope[2], m_linearSlope[1], m_linearSlope[0]);
    const __m128 mm_lino = _mm_set_ps(0.0f, m_linearOffset[2], m_linearOffset[1], m_linearOffset[0]);
    const __m128 breakPnt = _mm_set_ps(0.f, m_linb[2], m_linb[1], m_linb[0]);

    __m128 mm_pixel;
    __m128 mm_pixel_lin;

    for (long idx = 0; idx<numPixels; ++idx)
    {
        mm_pixel = _mm_set_ps(0.0f, in[2], in[1], in[0]);
        __m128 flag = _mm_cmpgt_ps(mm_pixel, breakPnt);

        mm_pixel_lin = _mm_mul_ps(mm_pixel, mm_lins);
        mm_pixel_lin = _mm_add_ps(mm_pixel_lin, mm_lino);

        mm_pixel = _mm_mul_ps(mm_pixel, mm_m);
        mm_pixel = _mm_add_ps(mm_pixel, mm_b);
        mm_pixel = _mm_max_ps(mm_pixel, mm_minValue);
        mm_pixel = sseLog2(mm_pixel);
        mm_pixel = _mm_mul_ps(mm_pixel, mm_klog);
        mm_pixel = _mm_add_ps(mm_pixel, mm_kb);

        mm_pixel = _mm_or_ps(_mm_and_ps(flag, mm_pixel),
                             _mm_andnot_ps(flag, mm_pixel_lin));

        const float alphares = in[3];

        _mm_storeu_ps(out, mm_pixel);
        out[3] = alphares;

        out += 4;
        in += 4;
    }
}
#endif

} // namespace OCIO_NAMESPACE


switch (m_State)
    {
    case OBJECTIVESTATE_ALLIANCE:
        team = 0;
        break;
    case OBJECTIVESTATE_HORDE:
        team = 1;
        break;
    default:
        return;
    }

/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 */

#include "OutdoorPvP.h"
#include "CellImpl.h"
#include "ChatPackets.h"
#include "DatabaseEnv.h"
#include "DB2Stores.h"
#include "GridNotifiersImpl.h"
#include "Group.h"
#include "Log.h"
#include "Map.h"
#include "MapManager.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "OutdoorPvPMgr.h"
#include "WorldPacket.h"
#include "WorldSession.h"

class DefenseMessageBuilder
{
    public:
        DefenseMessageBuilder(uint32 zoneId, uint32 id)
            : _zoneId(zoneId), _id(id) { }

        Trinity::PacketSenderOwning<WorldPackets::Chat::DefenseMessage>* operator()(LocaleConstant locale) const
        {
            std::string text = sOutdoorPvPMgr->GetDefenseMessage(_zoneId, _id, locale);

            Trinity::PacketSenderOwning<WorldPackets::Chat::DefenseMessage>* defenseMessage = new Trinity::PacketSenderOwning<WorldPackets::Chat::DefenseMessage>();
            defenseMessage->Data.ZoneID = _zoneId;
            defenseMessage->Data.MessageText = text;
            defenseMessage->Data.Write();
            return defenseMessage;
        }

    private:
        uint32 _zoneId; ///< ZoneId
        uint32 _id;     ///< BroadcastTextId
};

OPvPCapturePoint::OPvPCapturePoint(OutdoorPvP* pvp):
    m_capturePointSpawnId(), m_capturePoint(nullptr), m_maxValue(0.0f), m_minValue(0.0f), m_maxSpeed(0),
    m_value(0), m_team(TEAM_NEUTRAL), m_OldState(OBJECTIVESTATE_NEUTRAL),
    m_State(OBJECTIVESTATE_NEUTRAL), m_neutralValuePct(0), m_PvP(pvp)
{ }

bool OPvPCapturePoint::HandlePlayerEnter(Player* player)
{
    if (m_capturePoint)
    {
        player->SendUpdateWorldState(m_capturePoint->GetGOInfo()->controlZone.worldState1, 1);
        player->SendUpdateWorldState(m_capturePoint->GetGOInfo()->controlZone.worldstate2, (uint32)ceil((m_value + m_maxValue) / (2 * m_maxValue) * 100.0f));
        player->SendUpdateWorldState(m_capturePoint->GetGOInfo()->controlZone.worldstate3, m_neutralValuePct);
    }
    return m_activePlayers[player->GetTeamId()].insert(player->GetGUID()).second;
}

void OPvPCapturePoint::HandlePlayerLeave(Player* player)
{
    if (m_capturePoint)
        player->SendUpdateWorldState(m_capturePoint->GetGOInfo()->controlZone.worldState1, 0);
    m_activePlayers[player->GetTeamId()].erase(player->GetGUID());
}

void OPvPCapturePoint::SendChangePhase()
{
    if (!m_capturePoint)
        return;

    // send this too, sometimes the slider disappears, dunno why :(
    SendUpdateWorldState(m_capturePoint->GetGOInfo()->controlZone.worldState1, 1);
    // send these updates to only the ones in this objective
    SendUpdateWorldState(m_capturePoint->GetGOInfo()->controlZone.worldstate2, (uint32)ceil((m_value + m_maxValue) / (2 * m_maxValue) * 100.0f));
    // send this too, sometimes it resets :S
    SendUpdateWorldState(m_capturePoint->GetGOInfo()->controlZone.worldstate3, m_neutralValuePct);
}

void OPvPCapturePoint::AddGO(uint32 type, ObjectGuid::LowType guid)
{
    GameObjectData const* data = sObjectMgr->GetGameObjectData(guid);
    if (!data)
        return;

    m_Objects[type] = guid;
    m_ObjectTypes[m_Objects[type]] = type;
}

void OPvPCapturePoint::AddCre(uint32 type, ObjectGuid::LowType guid)
{
    CreatureData const* data = sObjectMgr->GetCreatureData(guid);
    if (!data)
        return;

    m_Creatures[type] = guid;
    m_CreatureTypes[m_Creatures[type]] = type;
}

bool OPvPCapturePoint::AddObject(uint32 type, uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot)
{
    if (ObjectGuid::LowType guid = sObjectMgr->AddGameObjectData(entry, map, pos, rot, 0))
    {
        AddGO(type, guid);
        return true;
    }

    return false;
}

bool OPvPCapturePoint::AddCreature(uint32 type, uint32 entry, uint32 map, Position const& pos, TeamId /*teamId = TEAM_NEUTRAL*/, uint32 spawntimedelay /*= 0*/)
{
    if (ObjectGuid::LowType guid = sObjectMgr->AddCreatureData(entry, map, pos, spawntimedelay))
    {
        AddCre(type, guid);
        return true;
    }

    return false;
}

bool OPvPCapturePoint::SetCapturePointData(uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot)
{
    TC_LOG_DEBUG("outdoorpvp", "Creating capture point %u", entry);

    // check info existence
    GameObjectTemplate const* goinfo = sObjectMgr->GetGameObjectTemplate(entry);
    if (!goinfo || goinfo->type != GAMEOBJECT_TYPE_CONTROL_ZONE)
    {
        TC_LOG_ERROR("outdoorpvp", "OutdoorPvP: GO %u is not capture point!", entry);
        return false;
    }

    m_capturePointSpawnId = sObjectMgr->AddGameObjectData(entry, map, pos, rot, 0);
    if (!m_capturePointSpawnId)
        return false;

    // get the needed values from goinfo
    m_maxValue = (float)goinfo->controlZone.maxTime;
    m_maxSpeed = m_maxValue / (goinfo->controlZone.minTime ? goinfo->controlZone.minTime : 60);
    m_neutralValuePct = goinfo->controlZone.neutralPercent;
    m_minValue = CalculatePct(m_maxValue, m_neutralValuePct);

    return true;
}

bool OPvPCapturePoint::DelCreature(uint32 type)
{
    ObjectGuid::LowType spawnId = m_Creatures[type];
    if (!spawnId)
    {
        TC_LOG_DEBUG("outdoorpvp", "opvp creature type %u was already deleted", type);
        return false;
    }

    auto bounds = m_PvP->GetMap()->GetCreatureBySpawnIdStore().equal_range(spawnId);
    for (auto itr = bounds.first; itr != bounds.second;)
    {
        Creature* c = itr->second;
        ++itr;
        // Don't save respawn time
        c->SetRespawnTime(0);
        c->DespawnOrUnsummon();
        c->AddObjectToRemoveList();
    }

    TC_LOG_DEBUG("outdoorpvp", "deleting opvp creature type %u", type);
    // explicit removal from map
    // beats me why this is needed, but with the recent removal "cleanup" some creatures stay in the map if "properly" deleted
    // so this is a big fat workaround, if AddObjectToRemoveList and DoDelayedMovesAndRemoves worked correctly, this wouldn't be needed
    // delete respawn time for this creature
    CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN);
    stmt->setUInt64(0, spawnId);
    stmt->setUInt16(1, m_PvP->GetMap()->GetId());
    stmt->setUInt32(2, 0);  // instance id, always 0 for world maps
    CharacterDatabase.Execute(stmt);

    sObjectMgr->DeleteCreatureData(spawnId);
    m_CreatureTypes[m_Creatures[type]] = 0;
    m_Creatures[type] = 0;
    return true;
}

bool OPvPCapturePoint::DelObject(uint32 type)
{
    if (!m_Objects[type])
        return false;

    ObjectGuid::LowType spawnId = m_Objects[type];
    auto bounds = m_PvP->GetMap()->GetGameObjectBySpawnIdStore().equal_range(spawnId);
    for (auto itr = bounds.first; itr != bounds.second;)
    {
        GameObject* go = itr->second;
        ++itr;
        // Don't save respawn time
        go->SetRespawnTime(0);
        go->Delete();
    }
    sObjectMgr->DeleteGameObjectData(spawnId);
    m_ObjectTypes[m_Objects[type]] = 0;
    m_Objects[type] = 0;
    return true;
}

bool OPvPCapturePoint::DelCapturePoint()
{
    sObjectMgr->DeleteGameObjectData(m_capturePointSpawnId);
    m_capturePointSpawnId = 0;

    if (m_capturePoint)
    {
        m_capturePoint->SetRespawnTime(0);                                 // not save respawn time
        m_capturePoint->Delete();
    }

    return true;
}

void OPvPCapturePoint::DeleteSpawns()
{
    for (std::map<uint32, ObjectGuid::LowType>::iterator i = m_Objects.begin(); i != m_Objects.end(); ++i)
        DelObject(i->first);
    for (std::map<uint32, ObjectGuid::LowType>::iterator i = m_Creatures.begin(); i != m_Creatures.end(); ++i)
        DelCreature(i->first);
    DelCapturePoint();
}

void OutdoorPvP::DeleteSpawns()
{
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
    {
        itr->second->DeleteSpawns();
        delete itr->second;
    }
    m_capturePoints.clear();
}

OutdoorPvP::OutdoorPvP() : m_TypeId(0), m_sendUpdate(true), m_map(nullptr) { }

OutdoorPvP::~OutdoorPvP()
{
    DeleteSpawns();
}

void OutdoorPvP::HandlePlayerEnterZone(Player* player, uint32 /*zone*/)
{
    m_players[player->GetTeamId()].insert(player->GetGUID());
}

void OutdoorPvP::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/)
{
    // inform the objectives of the leaving
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
        itr->second->HandlePlayerLeave(player);
    // remove the world state information from the player (we can't keep everyone up to date, so leave out those who are not in the concerning zones)
    if (!player->GetSession()->PlayerLogout())
        SendRemoveWorldStates(player);
    m_players[player->GetTeamId()].erase(player->GetGUID());
    TC_LOG_DEBUG("outdoorpvp", "Player %s left an outdoorpvp zone", player->GetName().c_str());
}

void OutdoorPvP::HandlePlayerResurrects(Player* /*player*/, uint32 /*zone*/) { }

bool OutdoorPvP::Update(uint32 diff)
{
    bool objective_changed = false;
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
    {
        if (itr->second->Update(diff))
            objective_changed = true;
    }
    return objective_changed;
}

bool OPvPCapturePoint::Update(uint32 diff)
{
    if (!m_capturePoint)
        return false;

    float radius = (float)m_capturePoint->GetGOInfo()->controlZone.radius;

    for (uint32 team = 0; team < 2; ++team)
    {
        for (GuidSet::iterator itr = m_activePlayers[team].begin(); itr != m_activePlayers[team].end();)
        {
            ObjectGuid playerGuid = *itr;
            ++itr;

            if (Player* player = ObjectAccessor::FindPlayer(playerGuid))
                if (!m_capturePoint->IsWithinDistInMap(player, radius) || !player->IsOutdoorPvPActive())
                    HandlePlayerLeave(player);
        }
    }

    std::list<Player*> players;
    Trinity::AnyPlayerInObjectRangeCheck checker(m_capturePoint, radius);
    Trinity::PlayerListSearcher<Trinity::AnyPlayerInObjectRangeCheck> searcher(m_capturePoint, players, checker);
    Cell::VisitWorldObjects(m_capturePoint, searcher, radius);

    for (std::list<Player*>::iterator itr = players.begin(); itr != players.end(); ++itr)
    {
        Player* const player = *itr;
        if (player->IsOutdoorPvPActive())
        {
            if (m_activePlayers[player->GetTeamId()].insert(player->GetGUID()).second)
                HandlePlayerEnter(*itr);
        }
    }

    // get the difference of numbers
    float fact_diff = ((float)m_activePlayers[0].size() - (float)m_activePlayers[1].size()) * diff / OUTDOORPVP_OBJECTIVE_UPDATE_INTERVAL;
    if (!fact_diff)
        return false;

    uint32 Challenger = 0;
    float maxDiff = m_maxSpeed * diff;

    if (fact_diff < 0)
    {
        // horde is in majority, but it's already horde-controlled -> no change
        if (m_State == OBJECTIVESTATE_HORDE && m_value <= -m_maxValue)
            return false;

        if (fact_diff < -maxDiff)
            fact_diff = -maxDiff;

        Challenger = HORDE;
    }
    else
    {
        // ally is in majority, but it's already ally-controlled -> no change
        if (m_State == OBJECTIVESTATE_ALLIANCE && m_value >= m_maxValue)
            return false;

        if (fact_diff > maxDiff)
            fact_diff = maxDiff;

        Challenger = ALLIANCE;
    }

    float oldValue = m_value;
    TeamId oldTeam = m_team;

    m_OldState = m_State;

    m_value += fact_diff;

    if (m_value < -m_minValue) // red
    {
        if (m_value < -m_maxValue)
            m_value = -m_maxValue;
        m_State = OBJECTIVESTATE_HORDE;
        m_team = TEAM_HORDE;
    }
    else if (m_value > m_minValue) // blue
    {
        if (m_value > m_maxValue)
            m_value = m_maxValue;
        m_State = OBJECTIVESTATE_ALLIANCE;
        m_team = TEAM_ALLIANCE;
    }
    else if (oldValue * m_value <= 0) // grey, go through mid point
    {
        // if challenger is ally, then n->a challenge
        if (Challenger == ALLIANCE)
            m_State = OBJECTIVESTATE_NEUTRAL_ALLIANCE_CHALLENGE;
        // if challenger is horde, then n->h challenge
        else if (Challenger == HORDE)
            m_State = OBJECTIVESTATE_NEUTRAL_HORDE_CHALLENGE;
        m_team = TEAM_NEUTRAL;
    }
    else // grey, did not go through mid point
    {
        // old phase and current are on the same side, so one team challenges the other
        if (Challenger == ALLIANCE && (m_OldState == OBJECTIVESTATE_HORDE || m_OldState == OBJECTIVESTATE_NEUTRAL_HORDE_CHALLENGE))
            m_State = OBJECTIVESTATE_HORDE_ALLIANCE_CHALLENGE;
        else if (Challenger == HORDE && (m_OldState == OBJECTIVESTATE_ALLIANCE || m_OldState == OBJECTIVESTATE_NEUTRAL_ALLIANCE_CHALLENGE))
            m_State = OBJECTIVESTATE_ALLIANCE_HORDE_CHALLENGE;
        m_team = TEAM_NEUTRAL;
    }

    if (m_value != oldValue)
        SendChangePhase();

    if (m_OldState != m_State)
    {
        if (oldTeam != m_team)
            ChangeTeam(oldTeam);
        ChangeState();
        return true;
    }

    return false;
}

void OutdoorPvP::SendUpdateWorldState(uint32 field, uint32 value)
{
    if (m_sendUpdate)
        for (int i = 0; i < 2; ++i)
            for (GuidSet::iterator itr = m_players[i].begin(); itr != m_players[i].end(); ++itr)
                if (Player* const player = ObjectAccessor::FindPlayer(*itr))
                    player->SendUpdateWorldState(field, value);
}

void OPvPCapturePoint::SendUpdateWorldState(uint32 field, uint32 value)
{
    for (uint32 team = 0; team < 2; ++team)
    {
        // send to all players present in the area
        for (GuidSet::iterator itr = m_activePlayers[team].begin(); itr != m_activePlayers[team].end(); ++itr)
            if (Player* const player = ObjectAccessor::FindPlayer(*itr))
                player->SendUpdateWorldState(field, value);
    }
}

void OPvPCapturePoint::SendObjectiveComplete(uint32 id, ObjectGuid guid)
{
    uint32 team;

    // send to all players present in the area
    for (GuidSet::iterator itr = m_activePlayers[team].begin(); itr != m_activePlayers[team].end(); ++itr)
        if (Player* const player = ObjectAccessor::FindPlayer(*itr))
            player->KilledMonsterCredit(id, guid);
}

void OutdoorPvP::HandleKill(Player* killer, Unit* killed)
{
    if (Group* group = killer->GetGroup())
    {
        for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
        {
            Player* groupGuy = itr->GetSource();

            if (!groupGuy)
                continue;

            // skip if too far away
            if (!groupGuy->IsAtGroupRewardDistance(killed))
                continue;

            // creature kills must be notified, even if not inside objective / not outdoor pvp active
            // player kills only count if active and inside objective
            if ((groupGuy->IsOutdoorPvPActive() && IsInsideObjective(groupGuy)) || killed->GetTypeId() == TYPEID_UNIT)
                HandleKillImpl(groupGuy, killed);
        }
    }
    else
    {
        // creature kills must be notified, even if not inside objective / not outdoor pvp active
        if ((killer->IsOutdoorPvPActive() && IsInsideObjective(killer)) || killed->GetTypeId() == TYPEID_UNIT)
            HandleKillImpl(killer, killed);
    }
}

bool OutdoorPvP::IsInsideObjective(Player* player) const
{
    for (OPvPCapturePointMap::const_iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
        if (itr->second->IsInsideObjective(player))
            return true;

    return false;
}

bool OPvPCapturePoint::IsInsideObjective(Player* player) const
{
    GuidSet const& plSet = m_activePlayers[player->GetTeamId()];
    return plSet.find(player->GetGUID()) != plSet.end();
}

bool OutdoorPvP::HandleCustomSpell(Player* player, uint32 spellId, GameObject* go)
{
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
        if (itr->second->HandleCustomSpell(player, spellId, go))
            return true;

    return false;
}

bool OPvPCapturePoint::HandleCustomSpell(Player* player, uint32 /*spellId*/, GameObject* /*go*/)
{
    if (!player->IsOutdoorPvPActive())
        return false;
    return true;
}

bool OutdoorPvP::HandleOpenGo(Player* player, GameObject* go)
{
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
        if (itr->second->HandleOpenGo(player, go) >= 0)
            return true;

    return false;
}

bool OutdoorPvP::HandleGossipOption(Player* player, Creature* creature, uint32 id)
{
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
        if (itr->second->HandleGossipOption(player, creature, id))
            return true;

    return false;
}

bool OutdoorPvP::CanTalkTo(Player* player, Creature* c, GossipMenuItems const& gso)
{
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
        if (itr->second->CanTalkTo(player, c, gso))
            return true;

    return false;
}

bool OutdoorPvP::HandleDropFlag(Player* player, uint32 id)
{
    for (OPvPCapturePointMap::iterator itr = m_capturePoints.begin(); itr != m_capturePoints.end(); ++itr)
        if (itr->second->HandleDropFlag(player, id))
            return true;

    return false;
}

int32 OPvPCapturePoint::HandleOpenGo(Player* /*player*/, GameObject* go)
{
    std::map<ObjectGuid::LowType, uint32>::iterator itr = m_ObjectTypes.find(go->GetSpawnId());
    if (itr != m_ObjectTypes.end())
        return itr->second;

    return -1;
}

void OutdoorPvP::BroadcastPacket(WorldPacket const* data) const
{
    // This is faster than sWorld->SendZoneMessage
    for (uint32 team = 0; team < 2; ++team)
        for (GuidSet::const_iterator itr = m_players[team].begin(); itr != m_players[team].end(); ++itr)
            if (Player* const player = ObjectAccessor::FindPlayer(*itr))
                player->SendDirectMessage(data);
}

void OutdoorPvP::AddCapturePoint(OPvPCapturePoint* cp)
{
    OPvPCapturePointMap::iterator i = m_capturePoints.find(cp->m_capturePointSpawnId);
    if (i != m_capturePoints.end())
    {
        TC_LOG_ERROR("outdoorpvp", "OutdoorPvP::AddCapturePoint: CapturePoint " UI64FMTD " already exists!", cp->m_capturePointSpawnId);
        delete i->second;
    }
    m_capturePoints[cp->m_capturePointSpawnId] = cp;
}

OPvPCapturePoint* OutdoorPvP::GetCapturePoint(ObjectGuid::LowType guid) const
{
    OutdoorPvP::OPvPCapturePointMap::const_iterator itr = m_capturePoints.find(guid);
    if (itr != m_capturePoints.end())
        return itr->second;
    return nullptr;
}

void OutdoorPvP::RegisterZone(uint32 zoneId)
{
    sOutdoorPvPMgr->AddZone(zoneId, this);
}

bool OutdoorPvP::HasPlayer(Player const* player) const
{
    GuidSet const& plSet = m_players[player->GetTeamId()];
    return plSet.find(player->GetGUID()) != plSet.end();
}

void OutdoorPvP::TeamCastSpell(TeamId team, int32 spellId)
{
    if (spellId > 0)
    {
        for (GuidSet::iterator itr = m_players[team].begin(); itr != m_players[team].end(); ++itr)
            if (Player* const player = ObjectAccessor::FindPlayer(*itr))
                player->CastSpell(player, (uint32)spellId, true);
    }
    else
    {
        for (GuidSet::iterator itr = m_players[team].begin(); itr != m_players[team].end(); ++itr)
            if (Player* const player = ObjectAccessor::FindPlayer(*itr))
                player->RemoveAura((uint32)-spellId); // by stack?
    }
}

void OutdoorPvP::TeamApplyBuff(TeamId team, uint32 spellId, uint32 spellId2)
{
    TeamCastSpell(team, spellId);
    TeamCastSpell(OTHER_TEAM(team), spellId2 ? -(int32)spellId2 : -(int32)spellId);
}

void OutdoorPvP::OnGameObjectCreate(GameObject* go)
{
    if (go->GetGoType() != GAMEOBJECT_TYPE_CONTROL_ZONE)
        return;

    if (OPvPCapturePoint *cp = GetCapturePoint(go->GetSpawnId()))
        cp->m_capturePoint = go;
}

void OutdoorPvP::OnGameObjectRemove(GameObject* go)
{
    if (go->GetGoType() != GAMEOBJECT_TYPE_CONTROL_ZONE)
        return;

    if (OPvPCapturePoint *cp = GetCapturePoint(go->GetSpawnId()))
        cp->m_capturePoint = nullptr;
}

void OutdoorPvP::SendDefenseMessage(uint32 zoneId, uint32 id)
{
    DefenseMessageBuilder builder(zoneId, id);
    Trinity::LocalizedDo<DefenseMessageBuilder> localizer(builder);
    BroadcastWorker(localizer, zoneId);
}

template<class Worker>
void OutdoorPvP::BroadcastWorker(Worker& _worker, uint32 zoneId)
{
    for (uint32 i = 0; i < BG_TEAMS_COUNT; ++i)
        for (GuidSet::iterator itr = m_players[i].begin(); itr != m_players[i].end(); ++itr)
            if (Player* player = ObjectAccessor::FindPlayer(*itr))
                if (player->GetZoneId() == zoneId)
                    _worker(player);
}

void OutdoorPvP::SetMapFromZone(uint32 zone)
{
    AreaTableEntry const* areaTable = sAreaTableStore.AssertEntry(zone);
    Map* map = sMapMgr->CreateBaseMap(areaTable->ContinentID);
    ASSERT(!map->Instanceable());
    m_map = map;
}


static void freeHintsCallback(void *ptr) { sdsfree((sds)ptr); }

/*
 */

#include <pegasus/version.h>
#include <dsn/utility/strings.h>
#include <setjmp.h>
#include <signal.h>
#include <algorithm>
#include "args.h"
#include "command_executor.h"
#include "commands.h"
#include "base/pegasus_const.h"

std::map<std::string, command_executor *> s_commands_map;
shell_context s_global_context;
size_t s_max_name_length = 0;
size_t s_option_width = 70;

void print_help();
bool help_info(command_executor *e, shell_context *sc, arguments args)
{
    print_help();
    return true;
}

static command_executor commands[] = {
    {
        "help", "print help info", "", help_info,
    },
    {
        "version", "get the shell version", "", version,
    },
    {
        "cluster_info",
        "get the information of the cluster",
        "[-r|--resolve_ip] [-o|--output file_name] [-j|--json]",
        query_cluster_info,
    },
    {
        "app",
        "get the partition information for some specific app",
        "<app_name> [-d|--detailed] [-r|--resolve_ip] [-o|--output file_name] [-j|--json]",
        query_app,
    },
    {
        "app_disk",
        "get the disk usage information for some specific app",
        "<app_name> [-d|--detailed] [-r|--resolve_ip] [-j|--json] [-o|--output file_name]",
        app_disk,
    },
    {
        "ls",
        "list all apps",
        "[-a|-all] [-d|--detailed] [-j|--json] [-o|--output file_name]"
        "[-s|--status all|available|creating|dropping|dropped]",
        ls_apps,
    },
    {
        "nodes",
        "get the node status for this cluster",
        "[-d|--detailed] [-j|--json] [-r|--resolve_ip] [-u|--resource_usage]"
        "[-o|--output file_name] [-s|--status all|alive|unalive] [-q|--qps]",
        ls_nodes,
    },
    {
        "create",
        "create an app",
        "<app_name> [-p|--partition_count num] [-r|--replica_count num] "
        "[-e|--envs k1=v1,k2=v2...]",
        create_app,
    },
    {
        "drop", "drop an app", "<app_name> [-r|--reserve_seconds num]", drop_app,
    },
    {
        "recall", "recall an app", "<app_id> [new_app_name]", recall_app,
    },
    {
        "set_meta_level",
        "set the meta function level: stopped, blind, freezed, steady, lively",
        "<stopped|blind|freezed|steady|lively>",
        set_meta_level,
    },
    {
        "get_meta_level", "get the meta function level", "", get_meta_level,
    },
    {
        "balance",
        "send explicit balancer request for the cluster",
        "<-g|--gpid appid.pidx> <-p|--type move_pri|copy_pri|copy_sec> <-f|--from from_address> "
        "<-t|--to to_address>",
        balance,
    },
    {
        "propose",
        "send configuration proposals to cluster",
        "[-f|--force] "
        "<-g|--gpid appid.pidx> <-p|--type ASSIGN_PRIMARY|ADD_SECONDARY|DOWNGRADE_TO_INACTIVE...> "
        "<-t|--target node_to_exec_command> <-n|--node node_to_be_affected> ",
        propose,
    },
    {
        "use",
        "set the current app used for the data access commands",
        "[app_name]",
        use_app_as_current,
    },
    {
        "cc", "change to the specified cluster", "[cluster_name]", cc_command,
    },
    {
        "escape_all",
        "if escape all characters when printing key/value bytes",
        "[true|false]",
        process_escape_all,
    },
    {
        "timeout",
        "default timeout in milliseconds for read/write operations",
        "[time_in_ms]",
        process_timeout,
    },
    {
        "hash",
        "calculate the hash result for some hash key",
        "<hash_key> <sort_key>",
        calculate_hash_value,
    },
    {
        "set", "set value", "<hash_key> <sort_key> <value> [ttl_in_seconds]", data_operations,
    },
    {
        "multi_set",
        "set multiple values for a single hash key",
        "<hash_key> <sort_key> <value> [sort_key value...]",
        data_operations,
    },
    {
        "get", "get value", "<hash_key> <sort_key>", data_operations,
    },
    {
        "multi_get",
        "get multiple values for a single hash key",
        "<hash_key> [sort_key...]",
        data_operations,
    },
    {
        "multi_get_range",
        "get multiple values under sort key range for a single hash key",
        "<hash_key> <start_sort_key> <stop_sort_key> "
        "[-a|--start_inclusive true|false] [-b|--stop_inclusive true|false] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix] "
        "[-y|--sort_key_filter_pattern str] "
        "[-n|--max_count num] [-i|--no_value] [-r|--reverse]",
        data_operations,
    },
    {
        "multi_get_sortkeys",
        "get multiple sort keys for a single hash key",
        "<hash_key>",
        data_operations,
    },
    {
        "del", "delete a key", "<hash_key> <sort_key>", data_operations,
    },
    {
        "multi_del",
        "delete multiple values for a single hash key",
        "<hash_key> <sort_key> [sort_key...]",
        data_operations,
    },
    {
        "multi_del_range",
        "delete multiple values under sort key range for a single hash key",
        "<hash_key> <start_sort_key> <stop_sort_key> "
        "[-a|--start_inclusive true|false] [-b|--stop_inclusive true|false] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix] "
        "[-y|--sort_key_filter_pattern str] "
        "[-o|--output file_name] [-i|--silent]",
        data_operations,
    },
    {
        "incr",
        "atomically increment value of a key",
        "<hash_key> <sort_key> [increment]",
        data_operations,
    },
    {
        "check_and_set",
        "atomically check and set value",
        "<hash_key> "
        "[-c|--check_sort_key str] "
        "[-t|--check_type not_exist|not_exist_or_empty|exist|not_empty] "
        "[match_anywhere|match_prefix|match_postfix] "
        "[bytes_less|bytes_less_or_equal|bytes_equal|bytes_greater_or_equal|bytes_greater] "
        "[int_less|int_less_or_equal|int_equal|int_greater_or_equal|int_greater] "
        "[-o|--check_operand str] "
        "[-s|--set_sort_key str] "
        "[-v|--set_value str] "
        "[-l|--set_value_ttl_seconds num] "
        "[-r|--return_check_value]",
        data_operations,
    },
    {
        "check_and_mutate",
        "atomically check and mutate",
        "<hash_key> "
        "[-c|--check_sort_key str] "
        "[-t|--check_type not_exist|not_exist_or_empty|exist|not_empty] "
        "[match_anywhere|match_prefix|match_postfix] "
        "[bytes_less|bytes_less_or_equal|bytes_equal|bytes_greater_or_equal|bytes_greater] "
        "[int_less|int_less_or_equal|int_equal|int_greater_or_equal|int_greater] "
        "[-o|--check_operand str] "
        "[-r|--return_check_value]",
        data_operations,
    },
    {
        "exist", "check value exist", "<hash_key> <sort_key>", data_operations,
    },
    {
        "count", "get sort key count for a single hash key", "<hash_key>", data_operations,
    },
    {
        "ttl", "query ttl for a specific key", "<hash_key> <sort_key>", data_operations,
    },
    {
        "hash_scan",
        "scan all sorted keys for a single hash key",
        "<hash_key> <start_sort_key> <stop_sort_key> "
        "[-a|--start_inclusive true|false] [-b|--stop_inclusive true|false] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix] "
        "[-y|--sort_key_filter_pattern str] "
        "[-v|--value_filter_type anywhere|prefix|postfix|exact] "
        "[-z|--value_filter_pattern str] "
        "[-o|--output file_name] [-n|--max_count num] [-t|--timeout_ms num] "
        "[-d|--detailed] [-i|--no_value]",
        data_operations,
    },
    {
        "full_scan",
        "scan all hash keys",
        "[-h|--hash_key_filter_type anywhere|prefix|postfix] "
        "[-x|--hash_key_filter_pattern str] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix|exact] "
        "[-y|--sort_key_filter_pattern str] "
        "[-v|--value_filter_type anywhere|prefix|postfix|exact] "
        "[-z|--value_filter_pattern str] "
        "[-o|--output file_name] [-n|--max_count num] [-t|--timeout_ms num] "
        "[-d|--detailed] [-i|--no_value] [-p|--partition num]",
        data_operations,
    },
    {
        "copy_data",
        "copy app data",
        "<-c|--target_cluster_name str> <-a|--target_app_name str> "
        "[-p|--partition num] [-b|--max_batch_count num] [-t|--timeout_ms num] "
        "[-h|--hash_key_filter_type anywhere|prefix|postfix] "
        "[-x|--hash_key_filter_pattern str] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix|exact] "
        "[-y|--sort_key_filter_pattern str] "
        "[-v|--value_filter_type anywhere|prefix|postfix|exact] "
        "[-z|--value_filter_pattern str] "
        "[-n|--no_overwrite] [-i|--no_value] [-g|--geo_data] "
        "[-e|--no_ttl]",
        data_operations,
    },
    {
        "clear_data",
        "clear app data",
        "[-p|--partition num] [-b|--max_batch_count num] [-t|--timeout_ms num] "
        "[-h|--hash_key_filter_type anywhere|prefix|postfix] "
        "[-x|--hash_key_filter_pattern str] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix|exact] "
        "[-y|--sort_key_filter_pattern str] "
        "[-v|--value_filter_type anywhere|prefix|postfix|exact] "
        "[-z|--value_filter_pattern str] "
        "[-f|--force]",
        data_operations,
    },
    {
        "count_data",
        "get app row count",
        "[-c|--precise][-p|--partition num] "
        "[-b|--max_batch_count num][-t|--timeout_ms num] "
        "[-h|--hash_key_filter_type anywhere|prefix|postfix] "
        "[-x|--hash_key_filter_pattern str] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix|exact] "
        "[-y|--sort_key_filter_pattern str] "
        "[-v|--value_filter_type anywhere|prefix|postfix|exact] "
        "[-z|--value_filter_pattern str][-d|--diff_hash_key] "
        "[-a|--stat_size] [-n|--top_count num] [-r|--run_seconds num]",
        data_operations,
    },
    {
        "remote_command",
        "send remote command to servers",
        "[-t all|meta-server|replica-server] [-r|--resolve_ip] [-l ip:port,ip:port...]"
        "<command> [arguments...]",
        remote_command,
    },
    {
        "server_info",
        "get info of servers",
        "[-t all|meta-server|replica-server] [-l ip:port,ip:port...] [-r|--resolve_ip]",
        server_info,
    },
    {
        "server_stat",
        "get stat of servers",
        "[-t all|meta-server|replica-server] [-l ip:port,ip:port...] [-r|--resolve_ip]",
        server_stat,
    },
    {
        "app_stat",
        "get stat of apps",
        "[-a|--app_name str] [-q|--only_qps] [-u|--only_usage] [-j|--json] "
        "[-o|--output file_name]",
        app_stat,
    },
    {
        "flush_log",
        "flush log of servers",
        "[-t all|meta-server|replica-server] [-l ip:port,ip:port...][-r|--resolve_ip]",
        flush_log,
    },
    {
        "local_get", "get value from local db", "<db_path> <hash_key> <sort_key>", local_get,
    },
    {
        "rdb_key_str2hex",
        "transform the given hashkey and sortkey to rocksdb raw key in hex representation",
        "<hash_key> <sort_key>",
        rdb_key_str2hex,
    },
    {
        "rdb_key_hex2str",
        "transform the given rocksdb raw key in hex representation to hash key and sort key",
        "<rdb_key_in_hex>",
        rdb_key_hex2str,
    },
    {
        "rdb_value_hex2str",
        "parse the given rocksdb raw value in hex representation",
        "<value_in_hex>",
        rdb_value_hex2str,
    },
    {
        "sst_dump",
        "dump sstable dir or files",
        "[--command=check|scan|none|raw] <--file=data_dir_OR_sst_file> "
        "[--from=user_key] [--to=user_key] [--read_num=num] [--show_properties] [--pegasus_data]",
        sst_dump,
    },
    {
        "mlog_dump",
        "dump mutation log dir",
        "<-i|--input log_dir> [-o|--output file_name] [-d|--detailed]",
        mlog_dump,
    },
    {
        "recover",
        "control the meta to recover the system from given nodes",
        "[-f|--node_list_file file_name] [-s|--node_list_str str] "
        "[-w|--wait_seconds num] "
        "[-b|--skip_bad_nodes] [-l|--skip_lost_partitions] [-o|--output file_name]",
        recover,
    },
    {
        "add_backup_policy",
        "add new cold backup policy",
        "<-p|--policy_name str> <-b|--backup_provider_type str> <-a|--app_ids 1,2...> "
        "<-i|--backup_interval_seconds num> <-s|--start_time hour:minute> "
        "<-c|--backup_history_cnt num>",
        add_backup_policy,
    },
    {"ls_backup_policy", "list the names of the subsistent backup policies", "", ls_backup_policy},
    {
        "query_backup_policy",
        "query subsistent backup policy and last backup infos",
        "<-p|--policy_name p1,p2...> [-b|--backup_info_cnt num]",
        query_backup_policy,
    },
    {
        "modify_backup_policy",
        "modify the backup policy",
        "<-p|--policy_name str> [-a|--add_app 1,2...] [-r|--remove_app 1,2...] "
        "[-i|--backup_interval_seconds num] [-c|--backup_history_count num] "
        "[-s|--start_time hour:minute]",
        modify_backup_policy,
    },
    {
        "disable_backup_policy",
        "stop policy continue backup",
        "<-p|--policy_name str>",
        disable_backup_policy,
    },
    {
        "enable_backup_policy",
        "start backup policy to backup again",
        "<-p|--policy_name str>",
        enable_backup_policy,
    },
    {
        "restore_app",
        "restore app from backup media",
        "<-c|--old_cluster_name str> <-p|--old_policy_name str> <-a|--old_app_name str> "
        "<-i|--old_app_id id> <-t|--timestamp/backup_id timestamp> "
        "<-b|--backup_provider_type str> [-n|--new_app_name str] [-s|--skip_bad_partition]",
        restore,
    },
    {
        "query_restore_status",
        "query restore status",
        "<restore_app_id> [-d|--detailed]",
        query_restore_status,
    },
    {
        "get_app_envs", "get current app envs", "[-j|--json]", get_app_envs,
    },
    {
        "set_app_envs", "set current app envs", "<key> <value> [key value...]", set_app_envs,
    },
    {
        "del_app_envs", "delete current app envs", "<key> [key...]", del_app_envs,
    },
    {
        "clear_app_envs", "clear current app envs", "[-a|--all] [-p|--prefix str]", clear_app_envs,
    },
    {
        "ddd_diagnose",
        "diagnose three-dead partitions",
        "[-g|--gpid appid|appid.pidx] [-d|--diagnose] [-a|--auto_diagnose] "
        "[-s|--skip_prompt] [-o|--output file_name]",
        ddd_diagnose,
    },
    {"add_dup", "add duplication", "<app_name> <remote_cluster_name> [-f|--freezed]", add_dup},
    {"query_dup", "query duplication info", "<app_name> [-d|--detail]", query_dup},
    {"remove_dup", "remove duplication", "<app_name> <dup_id>", remove_dup},
    {"start_dup", "start duplication", "<app_name> <dup_id>", start_dup},
    {"pause_dup", "pause duplication", "<app_name> <dup_id>", pause_dup},
    {"set_dup_fail_mode",
     "set fail_mode of duplication",
     "<app_name> <dup_id> <slow|skip>",
     set_dup_fail_mode},
    {
        "start_bulk_load",
        "start app bulk load",
        "<-a --app_name str> <-c --cluster_name str> <-p --file_provider_type str> <-r "
        "--root_path>",
        start_bulk_load,
    },
    {
        "query_bulk_load_status",
        "query app bulk load status",
        "<-a --app_name str> [-i --partition_index num] [-d --detailed]",
        query_bulk_load_status,
    },
    {
        "pause_bulk_load", "pause app bulk load", "<-a --app_name str>", pause_bulk_load,
    },
    {
        "restart_bulk_load", "restart app bulk load", "<-a --app_name str>", restart_bulk_load,
    },
    {
        "cancel_bulk_load",
        "cancel app bulk load",
        "<-a --app_name str> [-f --forced]",
        cancel_bulk_load,
    },
    {
        "detect_hotkey",
        "start or stop hotkey detection on a replica of a replica server",
        "<-a|--app_id num> "
        "<-p|--partition_index num> "
        "<-t|--hotkey_type read|write> "
        "<-c|--detect_action start|stop|query> "
        "<-d|--address str>",
        detect_hotkey,
    },
    {
        "exit", "exit shell", "", exit_shell,
    },
    {
        nullptr, nullptr, nullptr, nullptr,
    }};

void print_help(command_executor *e, size_t name_width, size_t option_width)
{
    std::vector<std::string> lines;
    std::string options(e->option_usage);
    int line_start = 0;
    int line_end = -1;
    int i;
    for (i = 0; i < options.size(); i++) {
        if (i - line_start >= option_width && line_end >= line_start) {
            std::string s = options.substr(line_start, line_end - line_start + 1);
            std::string r = dsn::utils::trim_string((char *)s.c_str());
            if (!r.empty())
                lines.push_back(r);
            line_start = line_end + 2;
        }
        if ((options[i] == ']' || options[i] == '>') && i < options.size() - 1 &&
            options[i + 1] == ' ') {
            line_end = i;
        }
    }
    line_end = i - 1;
    if (line_end >= line_start) {
        std::string s = options.substr(line_start, line_end - line_start + 1);
        std::string r = dsn::utils::trim_string((char *)s.c_str());
        if (!r.empty())
            lines.push_back(r);
    }

    std::cout << "\t" << e->name << std::string(name_width + 2 - strlen(e->name), ' ');
    if (lines.empty()) {
        std::cout << std::endl;
    } else {
        for (int k = 0; k < lines.size(); k++) {
            if (k != 0)
                std::cout << "\t" << std::string(name_width + 2, ' ');
            std::cout << lines[k] << std::endl;
        }
    }
}

void print_help()
{
    std::cout << "Usage:" << std::endl;
    for (int i = 0; commands[i].name != nullptr; ++i) {
        print_help(&commands[i], s_max_name_length, s_option_width);
    }
}

void register_all_commands()
{
    for (int i = 0; commands[i].name != nullptr; ++i) {
        auto pr = s_commands_map.emplace(commands[i].name, &commands[i]);
        dassert(pr.second, "the command '%s' is already registered!!!", commands[i].name);
        s_max_name_length = std::max(s_max_name_length, strlen(commands[i].name));
    }
}

void execute_command(command_executor *e, int argc, sds *str_args)
{
    if (!e->exec(e, &s_global_context, {argc, str_args})) {
        printf("USAGE: ");
        print_help(e, s_max_name_length, s_option_width);
    }
}

/* Linenoise completion callback. */
static void completionCallback(const char *buf, linenoiseCompletions *lc)
{
    for (int i = 0; commands[i].name != nullptr; ++i) {
        const command_executor &c = commands[i];

        size_t matchlen = strlen(buf);
        if (strncasecmp(buf, c.name, matchlen) == 0) {
            linenoiseAddCompletion(lc, c.name);
        }
    }
}

/* Linenoise hints callback. */
static char *hintsCallback(const char *buf, int *color, int *bold)
{
    int argc;
    sds *argv = sdssplitargs(buf, &argc);
    auto cleanup = dsn::defer([argc, argv]() { sdsfreesplitres(argv, argc); });

    /* Check if the argument list is empty and return ASAP. */
    if (argc == 0) {
        return nullptr;
    }

    size_t buflen = strlen(buf);
    bool endWithSpace = buflen && isspace(buf[buflen - 1]);

    for (int i = 0; commands[i].name != nullptr; ++i) {
        if (strcasecmp(argv[0], commands[i].name) == 0) {
            *color = 90;
            *bold = 0;
            sds hint = sdsnew(commands[i].option_usage);

            /* Add an initial space if needed. */
            if (!endWithSpace) {
                sds newhint = sdsnewlen(" ", 1);
                newhint = sdscatsds(newhint, hint);
                sdsfree(hint);
                hint = newhint;
            }

            return hint;
        }
    }
    return nullptr;
}

/* Linenoise free hints callback. */

/*extern*/ void check_in_cluster(std::string cluster_name)
{
    s_global_context.current_cluster_name = cluster_name;
    std::string server_list =
        dsn_config_get_value_string(pegasus::PEGASUS_CLUSTER_SECTION_NAME.c_str(),
                                    s_global_context.current_cluster_name.c_str(),
                                    "",
                                    "");

    dsn::replication::replica_helper::load_meta_servers(
        s_global_context.meta_list,
        pegasus::PEGASUS_CLUSTER_SECTION_NAME.c_str(),
        cluster_name.c_str());
    s_global_context.ddl_client =
        dsn::make_unique<dsn::replication::replication_ddl_client>(s_global_context.meta_list);

    // get real cluster name from zk
    std::string name;
    ::dsn::error_code err = s_global_context.ddl_client->cluster_name(1000, name);
    if (err == dsn::ERR_OK) {
        cluster_name = name;
    }
    std::cout << "The cluster name is: " << cluster_name << std::endl;
    std::cout << "The cluster meta list is: " << server_list << std::endl;
}

void initialize(int argc, char **argv)
{
    std::cout << "Pegasus Shell " << PEGASUS_VERSION << std::endl;
    std::cout << "Type \"help\" for more information." << std::endl;
    std::cout << "Type \"Ctrl-D\" or \"Ctrl-C\" to exit the shell." << std::endl;
    std::cout << std::endl;

    std::string config_file = argc > 1 ? argv[1] : "config.ini";
    if (!pegasus::pegasus_client_factory::initialize(config_file.c_str())) {
        std::cout << "ERROR: init pegasus failed: " << config_file << std::endl;
        dsn_exit(-1);
    } else {
        std::cout << "The config file is: " << config_file << std::endl;
    }

    std::string cluster_name = argc > 2 ? argv[2] : "mycluster";
    check_in_cluster(cluster_name);

    linenoiseSetMultiLine(1);
    linenoiseSetCompletionCallback(completionCallback);
    linenoiseSetHintsCallback(hintsCallback);
    linenoiseSetFreeHintsCallback(freeHintsCallback);
    linenoiseHistoryLoad(".shell-history");

    register_all_commands();
}

void run()
{
    while (true) {
        int arg_count = 0;
        sds *args = scanfCommand(&arg_count);
        auto cleanup = dsn::defer([args, arg_count] { sdsfreesplitres(args, arg_count); });

        if (args == nullptr) {
            printf("Invalid argument(s)\n");
            continue;
        }

        if (arg_count > 0) {
            auto iter = s_commands_map.find(args[0]);
            if (iter != s_commands_map.end()) {
                // command executions(e.g. check_and_mutate) may have the different hints, so cancel
                // the commands hints temporarily
                linenoiseSetHintsCallback(nullptr);
                execute_command(iter->second, arg_count, args);
                linenoiseSetHintsCallback(hintsCallback);
            } else {
                std::cout << "ERROR: invalid subcommand '" << args[0] << "'" << std::endl;
                print_help();
            }
        }
    }
}

int main(int argc, char **argv)
{
    initialize(argc, argv);
    run();
    return 0;
}