/*
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at http://live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

#include "LAppView.hpp"
#include "LAppDefine.hpp"
#include "LAppPal.hpp"
#include "LAppModel.hpp"

using namespace Csm;
using namespace LAppDefine;

USING_NS_CC;

LAppView::LAppView() : Node(), _debugRects(NULL), _model(NULL) {}

LAppView::~LAppView() {
    CC_SAFE_DELETE(_model);
}

void LAppView::onEnter() {
    // デバイス座標からスクリーン座標に変換するための
    deviceToScreen = new CubismMatrix44();

    // 画面の表示の拡大縮小や移動の変換を行う行列
    viewMatrix = new CubismViewMatrix();

    Size size = Director::getInstance()->getWinSize();

    float width = size.width;
    float height = size.height;
    float ratio = (float) height / width;
    float left = ViewLogicalLeft;
    float right = ViewLogicalRight;
    float bottom = -ratio;
    float top = ratio;

    viewMatrix->SetScreenRect(left, right, bottom, top); // デバイスに対応する画面の範囲。 Xの左端, Xの右端, Yの下端, Yの上端

    float screenW = abs(left - right);
    deviceToScreen->ScaleRelative(screenW / width, -screenW / width);
    deviceToScreen->TranslateRelative(-width / 2.0f, -height / 2.0f);

    // 表示範囲の設定
    viewMatrix->SetMaxScale(ViewMaxScale); // 限界拡大率
    viewMatrix->SetMinScale(ViewMinScale); // 限界縮小率

    // 表示できる最大範囲
    viewMatrix->SetMaxScreenRect(
            ViewLogicalMaxLeft,
            ViewLogicalMaxRight,
            ViewLogicalMaxBottom,
            ViewLogicalMaxTop
    );
}

void LAppView::onExit() {
    delete deviceToScreen;
    delete viewMatrix;
}

void LAppView::draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t flags) {
    _customCommand.init(_globalZOrder);
    _customCommand.func = CC_CALLBACK_0(LAppView::onDraw, this, transform, flags);
    renderer->addCommand(&_customCommand);
}

void LAppView::onDraw(const cocos2d::Mat4 &transform, uint32_t flags) {
    if (!_model)
        return;

    Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    Director::getInstance()->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform);

    CubismMatrix44 projection;
    Director *director = Director::getInstance();
    Size window = director->getWinSize();
    projection.Scale(1, window.width / window.height);

    // 移動
    if (viewMatrix != NULL) {
        viewMatrix->SetMatrix((csmFloat32 *) (transform.m));
        viewMatrix->Translate(
                (transform.m[12] * 2.0f - window.width) / window.width,
                (transform.m[13] * 2.0f - window.height) / window.width
        );
    }
    _model->GetModelMatrix()->SetMatrix((csmFloat32 *) (transform.m));
    _model->GetModelMatrix()->Translate(
            (transform.m[12] * 2.0f - window.width) / window.width,
            (transform.m[13] * 2.0f - window.height) / window.width
    );

    _model->Update();
    _model->Draw(projection);///< 参照渡しなのでprojectionは変質する

    if (_debugRects) {
        drawDebugRects();
    }

    Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}

float LAppView::transformViewX(float deviceX) {
    float screenX = deviceToScreen->TransformX(deviceX); // 論理座標変換した座標を取得。
    return viewMatrix->InvertTransformX(screenX); // 拡大、縮小、移動後の値。
}

float LAppView::transformViewY(float deviceY) {
    float screenY = deviceToScreen->TransformY(deviceY); // 論理座標変換した座標を取得。
    return viewMatrix->InvertTransformY(screenY); // 拡大、縮小、移動後の値。
}

float LAppView::transformScreenX(float deviceX) {
    return deviceToScreen->TransformX(deviceX);
}

float LAppView::transformScreenY(float deviceY) {
    return deviceToScreen->TransformY(deviceY);
}

void LAppView::setDebugRectsNode(DrawNode *debugRects) {
    _debugRects = debugRects;
}

void LAppView::drawDebugRects() const {
    const Color4F hitAreaColor = Color4F(1.0f, 0, 0, 0.2f);
    const Color4F userDataAreaColor = Color4F(0, 0, 1.0f, 0.2f);

    CubismMatrix44 projection;
    const Size window = Director::getInstance()->getWinSize();
    const CubismVector2 windowSize = CubismVector2(window.width, window.height);
    projection.Scale(1, window.width / window.height);

    if (viewMatrix != NULL) {
        projection.MultiplyByMatrix(viewMatrix);
    }

    _debugRects->clear();
    const Csm::csmVector<Csm::csmRectF> &userDataAreas = _model->GetUserDataAreas(projection,
                                                                                  windowSize);
    for (csmUint32 j = 0; j < userDataAreas.GetSize(); ++j) {
        _debugRects->drawSolidRect(Vec2(userDataAreas[j].X, userDataAreas[j].Y),
                                   Vec2(userDataAreas[j].GetRight(), userDataAreas[j].GetBottom()),
                                   userDataAreaColor);
    }

    const Csm::csmVector<Csm::csmRectF> &hitAreas = _model->GetHitAreas(projection, windowSize);
    for (csmUint32 j = 0; j < hitAreas.GetSize(); ++j) {
        _debugRects->drawSolidRect(Vec2(hitAreas[j].X, hitAreas[j].Y),
                                   Vec2(hitAreas[j].GetRight(), hitAreas[j].GetBottom()),
                                   hitAreaColor);
    }
}

bool LAppView::setModel(std::string dir, std::string fileName) {
    if (_model)CC_SAFE_DELETE(_model);
    _model = new LAppModel();
    if (dir[dir.length() - 1] != '/') {
        dir += "/";
    }
    _model->LoadAssets(dir.c_str(), fileName.c_str());
    return true;
}

void LAppView::setPosition(float dx, float dy) {
    if (_model) {
        // 移動
        viewMatrix->Translate(dx, dy);
    }
}

void LAppView::setScale(float sx, float sy) {
    if (_model) {
        // 移動
        viewMatrix->Scale(sx, sy);
    }
}

void LAppView::touchBegan(const cocos2d::Vec2 &pt) {
    if (DebugTouchLogEnable)LAppPal::PrintLog("[APP]touchesBegan x:%.0f y:%.0f", pt.x, pt.y);
    touchMgr->touchesBegan(pt.x, pt.y);
}

void LAppView::touchMoved(const cocos2d::Vec2 &pt) {
    float screenX = this->transformScreenX(touchMgr->getX());
    float screenY = this->transformScreenY(touchMgr->getY());
    float viewX = this->transformViewX(touchMgr->getX());
    float viewY = this->transformViewY(touchMgr->getY());

    if (DebugTouchLogEnable)
        LAppPal::PrintLog(
                "[APP]touchesMoved device{x:%.0f y:%.0f} screen{x:%.2f y:%.2f} view{x:%.2f y:%.2f}",
                pt.x, pt.y, screenX, screenY, viewX, viewY);

    touchMgr->touchesMoved(pt.x, pt.y);

    if (_model) {
        _model->SetDragging(viewX, viewY);
    }
}

void LAppView::touchEnded(const cocos2d::Vec2 &position) {
    if (_model) {
        _model->SetDragging(0, 0);
    }

    // シングルタップ
    float x = deviceToScreen->TransformX(touchMgr->getX()); // 論理座標変換した座標を取得。
    float y = deviceToScreen->TransformY(touchMgr->getY()); // 論理座標変換した座標を取得。
    if (DebugTouchLogEnable) LAppPal::PrintLog("[APP]touchesEnded x:%.2f y:%.2f", x, y);
    if (_model) {
        if (_model->HitTest(HitAreaNameHead, x, y)) {
            if (DebugLogEnable) LAppPal::PrintLog("[APP]hit area: [%s]", HitAreaNameHead);
            _model->SetRandomExpression();
        } else if (_model->HitTest(HitAreaNameBody, x, y)) {
            if (DebugLogEnable) LAppPal::PrintLog("[APP]hit area: [%s]", HitAreaNameBody);
            _model->StartRandomMotion(MotionGroupTapBody, PriorityNormal);
        }
    }
}

void LAppView::StartMotion(const char *group, int no, int priority) {
    if (_model) {
        _model->StartMotion(group, no, priority);
    }
}

void LAppView::StartRandomMotion(const char *group, int priority) {
    if (_model) {
        _model->StartRandomMotion(group, priority);
    }
}

void LAppView::SetExpression(const char *expressionID) {
    if (_model) {
        _model->SetExpression(expressionID);
    }
}

void LAppView::SetRandomExpression() {
    if (_model) {
        _model->SetRandomExpression();
    }
}

float LAppView::GetCanvasWidth() const {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetCanvasWidth();
    }
}

float LAppView::GetCanvasHeight() const {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetCanvasHeight();
    }
}

int LAppView::GetPartCount() const {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetPartCount();
    }
}

void LAppView::SetPartOpacity(int partIndex, float opacity) {
    if (_model && _model->GetModel()) {
        _model->GetModel()->SetPartOpacity(partIndex, opacity);
    }
}

float LAppView::GetPartOpacity(int partIndex) {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetPartOpacity(partIndex);
    }
}

int LAppView::GetParameterCount() const {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetParameterCount();
    }
}

float LAppView::GetParameterMaximumValue(unsigned int parameterIndex) const {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetParameterMaximumValue(parameterIndex);
    }
}

float LAppView::GetParameterMinimumValue(unsigned int parameterIndex) const {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetParameterMinimumValue(parameterIndex);
    }
}

float LAppView::GetParameterDefaultValue(unsigned int parameterIndex) const {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetParameterDefaultValue(parameterIndex);
    }
}

float LAppView::GetParameterValue(int parameterIndex) {
    if (_model && _model->GetModel()) {
        _model->GetModel()->GetParameterValue(parameterIndex);
    }
}

void LAppView::SetParameterValue(int parameterIndex, float value, float weight) {
    if (_model && _model->GetModel()) {
        _model->GetModel()->SetParameterValue(parameterIndex, value, weight);
    }
}

void LAppView::AddParameterValue(int parameterIndex, float value, float weight) {
    if (_model && _model->GetModel()) {
        _model->GetModel()->AddParameterValue(parameterIndex, value, weight);
    }
}

void LAppView::MultiplyParameterValue(int parameterIndex, float value, float weight) {
    if (_model && _model->GetModel()) {
        _model->GetModel()->MultiplyParameterValue(parameterIndex, value, weight);
    }
}
