/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "frameworks/bridge/declarative_frontend/jsview/js_texttimer.h"

#include <regex>

#include "base/log/ace_scoring_log.h"
#include "bridge/declarative_frontend/engine/js_types.h"
#include "bridge/declarative_frontend/jsview/js_utils.h"
#include "bridge/declarative_frontend/jsview/js_view_common_def.h"
#include "bridge/declarative_frontend/jsview/models/text_timer_model_impl.h"
#include "core/components/common/layout/constants.h"
#include "core/components/declaration/texttimer/texttimer_declaration.h"
#include "core/components/text/text_theme.h"
#include "core/components_ng/base/view_stack_processor.h"
#include "core/components_ng/pattern/texttimer/text_timer_model.h"
#include "core/components_ng/pattern/texttimer/text_timer_model_ng.h"

namespace OHOS::Ace {

std::unique_ptr<TextTimerModel> TextTimerModel::instance_ = nullptr;
std::mutex TextTimerModel::mutex_;

TextTimerModel* TextTimerModel::GetInstance()
{
    if (!instance_) {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!instance_) {
#ifdef NG_BUILD
            instance_.reset(new NG::TextTimerModelNG());
#else
            if (Container::IsCurrentUseNewPipeline()) {
                instance_.reset(new NG::TextTimerModelNG());
            } else {
                instance_.reset(new Framework::TextTimerModelImpl());
            }
#endif
        }
    }
    return instance_.get();
}
} // namespace OHOS::Ace

namespace OHOS::Ace::Framework {
namespace {
const std::vector<FontStyle> FONT_STYLES = { FontStyle::NORMAL, FontStyle::ITALIC };
const std::string DEFAULT_FORMAT = "HH:mm:ss.SS";
constexpr double MAX_COUNT_DOWN = 86400000.0;
} // namespace

void JSTextTimer::Create(const JSCallbackInfo& info)
{
    auto controller = TextTimerModel::GetInstance()->Create();
    if (info.Length() < 1 || !info[0]->IsObject()) {
        SetFontDefault();
        return;
    }
    auto paramObject = JSRef<JSObject>::Cast(info[0]);
    auto tempIsCountDown = paramObject->GetProperty("isCountDown");
    if (tempIsCountDown->IsBoolean()) {
        bool isCountDown = tempIsCountDown->ToBoolean();
        TextTimerModel::GetInstance()->SetIsCountDown(isCountDown);
        if (isCountDown) {
            auto count = paramObject->GetProperty("count");
            if (count->IsNumber()) {
                auto inputCount = count->ToNumber<double>();
                if (inputCount > 0 && inputCount < MAX_COUNT_DOWN) {
                    TextTimerModel::GetInstance()->SetInputCount(inputCount);
                } else {
                    TextTimerModel::GetInstance()->SetInputCount(TIME_DEFAULT_COUNT);
                }
            }
            if (count->IsUndefined() || count->IsNull()) {
                TextTimerModel::GetInstance()->SetInputCount(TIME_DEFAULT_COUNT);
            }
        }
    }

    auto controllerObj = paramObject->GetProperty("controller");
    if (controllerObj->IsObject()) {
        auto* jsController = JSRef<JSObject>::Cast(controllerObj)->Unwrap<JSTextTimerController>();
        if (jsController) {
            jsController->SetInstanceId(Container::CurrentId());
            jsController->SetController(controller);
        }
    }
}

void JSTextTimer::JSBind(BindingTarget globalObj)
{
    JSClass<JSTextTimer>::Declare("TextTimer");
    MethodOptions opt = MethodOptions::NONE;
    JSClass<JSTextTimer>::StaticMethod("create", &JSTextTimer::Create, opt);
    JSClass<JSTextTimer>::StaticMethod("format", &JSTextTimer::SetFormat);
    JSClass<JSTextTimer>::StaticMethod("fontColor", &JSTextTimer::SetTextColor);
    JSClass<JSTextTimer>::StaticMethod("fontSize", &JSTextTimer::SetFontSize);
    JSClass<JSTextTimer>::StaticMethod("fontWeight", &JSTextTimer::SetFontWeight, opt);
    JSClass<JSTextTimer>::StaticMethod("fontStyle", &JSTextTimer::SetFontStyle, opt);
    JSClass<JSTextTimer>::StaticMethod("fontFamily", &JSTextTimer::SetFontFamily, opt);
    JSClass<JSTextTimer>::StaticMethod("onTimer", &JSTextTimer::OnTimer);
    JSClass<JSTextTimer>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
    JSClass<JSTextTimer>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
    JSClass<JSTextTimer>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
    JSClass<JSTextTimer>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
    JSClass<JSTextTimer>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
    JSClass<JSTextTimer>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
    JSClass<JSTextTimer>::StaticMethod("textShadow", &JSTextTimer::SetTextShadow, opt);
    JSClass<JSTextTimer>::InheritAndBind<JSViewAbstract>(globalObj);
}

void JSTextTimer::SetFontDefault()
{
    RefPtr<TextTheme> textTheme = GetTheme<TextTheme>();
    TextTimerModel::GetInstance()->SetFontSize(textTheme->GetTextStyle().GetFontSize());
    TextTimerModel::GetInstance()->SetTextColor(textTheme->GetTextStyle().GetTextColor());
    TextTimerModel::GetInstance()->SetFontFamily(textTheme->GetTextStyle().GetFontFamilies());
    TextTimerModel::GetInstance()->SetFontWeight(textTheme->GetTextStyle().GetFontWeight());
    TextTimerModel::GetInstance()->SetItalicFontStyle(textTheme->GetTextStyle().GetFontStyle());
}

void JSTextTimer::SetFormat(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        return;
    }

    if (!info[0]->IsString()) {
        TextTimerModel::GetInstance()->SetFormat(DEFAULT_FORMAT);
        return;
    }

    auto format = info[0]->ToString();
    std::smatch result;
    std::regex pattern("(([YyMdD]+))");
    if (std::regex_search(format, result, pattern)) {
        if (!result.empty()) {
            format = DEFAULT_FORMAT;
        }
    }

    std::string target = "HmsS:.";
    for (auto ch : format) {
        if (target.find(ch) == std::string::npos) {
            format = DEFAULT_FORMAT;
        }
    }

    auto pos = format.find("hh");
    if (pos != std::string::npos) {
        format.replace(pos, sizeof("hh") - 1, "HH");
    }

    TextTimerModel::GetInstance()->SetFormat(format);
}

void JSTextTimer::SetFontSize(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        return;
    }
    auto pipelineContext = PipelineContext::GetCurrentContext();
    CHECK_NULL_VOID(pipelineContext);
    auto theme = pipelineContext->GetTheme<TextTheme>();
    CHECK_NULL_VOID(theme);

    CalcDimension fontSize;
    if (!ParseJsDimensionFp(info[0], fontSize)) {
        fontSize = theme->GetTextStyle().GetFontSize();
    }

    if (fontSize.IsNegative() || fontSize.Unit() == DimensionUnit::PERCENT) {
        auto pipelineContext = PipelineContext::GetCurrentContext();
        CHECK_NULL_VOID(pipelineContext);
        auto theme = pipelineContext->GetTheme<TextTheme>();
        CHECK_NULL_VOID(theme);
        fontSize = theme->GetTextStyle().GetFontSize();
    }

    TextTimerModel::GetInstance()->SetFontSize(fontSize);
}

void JSTextTimer::SetTextColor(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        return;
    }
    Color textColor;
    if (!ParseJsColor(info[0], textColor)) {
        auto pipelineContext = PipelineContext::GetCurrentContext();
        CHECK_NULL_VOID(pipelineContext);
        auto theme = pipelineContext->GetTheme<TextTheme>();
        textColor = theme->GetTextStyle().GetTextColor();
    }

    TextTimerModel::GetInstance()->SetTextColor(textColor);
}

void JSTextTimer::SetTextShadow(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        return;
    }
    std::vector<Shadow> shadows;
    ParseTextShadowFromShadowObject(info[0], shadows);
    if (!shadows.empty()) {
        TextTimerModel::GetInstance()->SetTextShadow(shadows);
    }
}

void JSTextTimer::SetFontWeight(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        return;
    }
    RefPtr<TextTheme> textTheme = GetTheme<TextTheme>();
    CHECK_NULL_VOID(textTheme);
    auto fontWeight = info[0];
    if (fontWeight->IsUndefined()) {
        TextTimerModel::GetInstance()->SetFontWeight(textTheme->GetTextStyle().GetFontWeight());
        return;
    }

    if (!fontWeight->IsNull()) {
        std::string weight;
        if (fontWeight->IsNumber()) {
            weight = std::to_string(fontWeight->ToNumber<int32_t>());
        } else {
            ParseJsString(fontWeight, weight);
        }
        TextTimerModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(weight));
    } else {
        TextTimerModel::GetInstance()->SetFontWeight(textTheme->GetTextStyle().GetFontWeight());
    }
}

void JSTextTimer::SetFontStyle(int32_t value)
{
    if (value < 0 || value >= static_cast<int32_t>(FONT_STYLES.size())) {
        return;
    }
    TextTimerModel::GetInstance()->SetItalicFontStyle(FONT_STYLES[value]);
}

void JSTextTimer::SetFontFamily(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        return;
    }
    std::vector<std::string> fontFamilies;
    if (!ParseJsFontFamilies(info[0], fontFamilies)) {
        return;
    }
    TextTimerModel::GetInstance()->SetFontFamily(fontFamilies);
}

void JSTextTimer::OnTimer(const JSCallbackInfo& info)
{
    CHECK_NULL_VOID(info[0]->IsFunction());
    auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(info[0]));
    WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
    auto onChange = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](
                        int64_t utc, int64_t elapsedTime) {
        JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
        ACE_SCORING_EVENT("TextTimer.onTimer");
        PipelineContext::SetCallBackNode(node);
        JSRef<JSVal> newJSVal[2];
        newJSVal[0] = JSRef<JSVal>::Make(ToJSValue(utc));
        newJSVal[1] = JSRef<JSVal>::Make(ToJSValue(elapsedTime));
        func->ExecuteJS(2, newJSVal);
    };
    TextTimerModel::GetInstance()->SetOnTimer(std::move(onChange));
}

void JSTextTimerController::JSBind(BindingTarget globalObj)
{
    JSClass<JSTextTimerController>::Declare("TextTimerController");
    JSClass<JSTextTimerController>::CustomMethod("start", &JSTextTimerController::Start);
    JSClass<JSTextTimerController>::CustomMethod("pause", &JSTextTimerController::Pause);
    JSClass<JSTextTimerController>::CustomMethod("reset", &JSTextTimerController::Reset);
    JSClass<JSTextTimerController>::Bind(
        globalObj, JSTextTimerController::Constructor, JSTextTimerController::Destructor);
}

void JSTextTimerController::Constructor(const JSCallbackInfo& info)
{
    auto timerController = Referenced::MakeRefPtr<JSTextTimerController>();
    timerController->IncRefCount();
    info.SetReturnValue(Referenced::RawPtr(timerController));
}

void JSTextTimerController::Destructor(JSTextTimerController* timerController)
{
    if (timerController != nullptr) {
        timerController->DecRefCount();
    }
}

void JSTextTimerController::Start(const JSCallbackInfo& info)
{
    ContainerScope scope(instanceId_);
    if (controller_) {
        controller_->Start();
    }
}

void JSTextTimerController::Pause(const JSCallbackInfo& info)
{
    ContainerScope scope(instanceId_);
    if (controller_) {
        controller_->Pause();
    }
}

void JSTextTimerController::Reset(const JSCallbackInfo& info)
{
    ContainerScope scope(instanceId_);
    if (controller_) {
        controller_->Reset();
    }
}
} // namespace OHOS::Ace::Framework