/*
 * 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 "adapter/preview/osal/resource_adapter_impl.h"

#include <set>

#include "base/i18n/localization.h"
#include "base/log/log.h"
#include "base/utils/system_properties.h"
#include "core/common/ace_application_info.h"
#include "core/common/container.h"
#include "core/components/common/layout/constants.h"
#include "core/components/theme/theme_attributes.h"

namespace OHOS::Ace {
namespace {

constexpr char COLOR_VALUE_PREFIX[] = "$color:";
constexpr char PATTERN_NAME_KEY_WORD[] = "$pattern:";
constexpr char STATE_VALUE_KEY_WORD[] = ".sxml";
constexpr char REF_ATTR_VALUE_KEY_WORD[] = "?theme:";
constexpr char STATE_CONTAINER[] = "state-container";
constexpr char STATE_ELEMENT[] = "element";
constexpr uint32_t STATE_MAX = 128;
constexpr double DPI_BASE = 160.0;
constexpr uint32_t THEME_ID_LIGHT = 117440515;
constexpr uint32_t THEME_ID_DARK = 117440516;

void CheckThemeId(int32_t& themeId)
{
    if (themeId >= 0) {
        return;
    }
    auto deviceType = SystemProperties::GetDeviceType();
    themeId = (deviceType == DeviceType::PHONE || deviceType == DeviceType::UNKNOWN || deviceType == DeviceType::CAR)
                  ? THEME_ID_LIGHT
                  : THEME_ID_DARK;
}

DimensionUnit ParseDimensionUnit(const std::string& unit)
{
    if (unit == "px") {
        return DimensionUnit::PX;
    } else if (unit == "fp") {
        return DimensionUnit::FP;
    } else if (unit == "lpx") {
        return DimensionUnit::LPX;
    } else if (unit == "%") {
        return DimensionUnit::PERCENT;
    } else {
        return DimensionUnit::VP;
    }
};

Global::Resource::ORIENTATION ConvertOrientation(DeviceOrientation orientation)
{
    return orientation == DeviceOrientation::PORTRAIT ? Global::Resource::ORIENTATION::ORIENTATION_PORTRAIT
                                                      : Global::Resource::ORIENTATION::ORIENTATION_LANDSCAPE;
}

Global::Resource::RESOLUTION ConvertResolution(double density)
{
    static const std::vector<std::pair<double, Global::Resource::RESOLUTION>> resolutions = {
        { 120.0, Global::Resource::RESOLUTION::RESOLUTION_LOW },
        { 160.0, Global::Resource::RESOLUTION::RESOLUTION_MEDIUM },
        { 240.0, Global::Resource::RESOLUTION::RESOLUTION_HIGH },
        { 320.0, Global::Resource::RESOLUTION::RESOLUTION_XHIGH },
        { 480.0, Global::Resource::RESOLUTION::RESOLUTION_XXHIGH },
        { 640.0, Global::Resource::RESOLUTION::RESOLUTION_XXXHIGH },
    };
    double deviceDpi = density * DPI_BASE;
    auto resolution = Global::Resource::RESOLUTION::RESOLUTION_LOW;
    for (const auto& [dpi, value] : resolutions) {
        resolution = value;
        if (LessOrEqual(deviceDpi, dpi)) {
            break;
        }
    }
    return resolution;
}

Global::Resource::DEVICE_TYPE ConvertDeviceType(DeviceType type)
{
    switch (type) {
        case DeviceType::PHONE:
            return Global::Resource::DEVICE_TYPE::DEVICE_TYPE_PHONE;
        case DeviceType::TV:
            return Global::Resource::DEVICE_TYPE::DEVICE_TYPE_TV;
        case DeviceType::WATCH:
            return Global::Resource::DEVICE_TYPE::DEVICE_TYPE_WATCH;
        case DeviceType::CAR:
            return Global::Resource::DEVICE_TYPE::DEVICE_TYPE_CAR;
        case DeviceType::TABLET:
            return Global::Resource::DEVICE_TYPE::DEVICE_TYPE_TABLET;
        default:
            return Global::Resource::DEVICE_TYPE::DEVICE_TYPE_UNDEFINED;
    }
}

Global::Resource::COLOR_MODE ConvertColorMode(ColorMode colorMode)
{
    return colorMode == ColorMode::LIGHT ? Global::Resource::COLOR_MODE::COLOR_MODE_LIGHT
                                         : Global::Resource::COLOR_MODE::COLOR_MODE_DARK;
}

Global::Resource::Configuration ConvertConfig(const ResourceConfiguration& config)
{
    Global::Resource::Configuration::Locale locale(AceApplicationInfo::GetInstance().GetLanguage(),
        AceApplicationInfo::GetInstance().GetCountryOrRegion(), AceApplicationInfo::GetInstance().GetScript());
    Global::Resource::Configuration globalConfig = {
        .locales_ = { locale },
        .orientation_ = ConvertOrientation(config.GetOrientation()),
        .resolution_ = ConvertResolution(config.GetDensity()),
        .deviceType_ = ConvertDeviceType(config.GetDeviceType()),
        .fontRatio_ = config.GetFontRatio(),
        .colorMode_ = ConvertColorMode(config.GetColorMode()),
    };
    return globalConfig;
}

RefPtr<StateResource> ParseStateResource(const std::string& styleName, const std::string& attrName,
    std::unique_ptr<Global::Resource::SolidXmlWrapper> xmlWrapper)
{
    auto rootNode = xmlWrapper->GetRoot();
    if (!rootNode) {
        LOGE("Parse %{public}s state resource %{public}s error! No root!", styleName.c_str(), attrName.c_str());
        return nullptr;
    }
    if (rootNode->GetNodeName() != STATE_CONTAINER) {
        return nullptr;
    }
    auto stateResource = AceType::MakeRefPtr<StateResource>();
    auto node = rootNode->GetChild();
    uint32_t stateCount = 0;
    while (node && ++stateCount < STATE_MAX) {
        // Parse each item
        auto nodeAttrs = node->GetAttributes();
        auto valueFindIter = nodeAttrs.find(STATE_ELEMENT);
        if (valueFindIter == nodeAttrs.end()) {
            continue;
        }
        auto stateColor = Color(valueFindIter->second.GetColorValue());
        uint32_t state = STATE_NORMAL;
        static const std::unordered_map<std::string, uint32_t> stateMap = {
            { "state_pressed", STATE_PRESSED },
            { "state_focus", STATE_FOCUS },
            { "state_checked", STATE_CHECKED },
            { "state_disabled", STATE_DISABLED },
            { "state_waiting", STATE_WAITING },
            { "state_hovered", STATE_HOVERED },
        };
        for (auto& [stateKey, stateValue] : nodeAttrs) {
            auto stateFindIter = stateMap.find(stateKey);
            if (stateFindIter == stateMap.end()) {
                continue;
            }
            if (stateValue.GetStringValue() != "true") {
                continue;
            }
            state |= stateFindIter->second;
        }
        stateResource->SetStateValue(state, { .type = ThemeConstantsType::COLOR, .value = stateColor });
        node = node->GetSibling();
    }
    return stateResource;
}
} // namespace

class RawThemeStyle : public ThemeStyle {
    DECLARE_ACE_TYPE(RawThemeStyle, ThemeStyle);

public:
    friend class ResourceAdapterImpl;
    using RawAttrMap = std::unordered_map<std::string, std::unique_ptr<Global::Resource::TypeAttribute>>;

    explicit RawThemeStyle(RefPtr<ResourceAdapter> resAdapter) : resAdapter_(resAdapter) {}
    ~RawThemeStyle() override = default;

    void ParseContent() override;

private:
    RawAttrMap rawAttrs_; // key and value read from global resource api.
    RefPtr<ResourceAdapter> resAdapter_;
};

void RawThemeStyle::ParseContent()
{
    static const std::set<std::string> stringAttrs = {
        "attr_text_font_family_regular",
        "attr_text_font_family_medium"
    };
    for (auto& [attrName, attrValue] : rawAttrs_) {
        if (!attrValue) {
            continue;
        }
        auto rawString = attrValue->GetOriginalValue();
        if (rawString.size() == 0) {
            continue;
        }
        if (rawString.front() == '#' || rawString.find(COLOR_VALUE_PREFIX) != std::string::npos) {
            // color
            attributes_[attrName] = { .type = ThemeConstantsType::COLOR, .value = Color(attrValue->GetColorValue()) };
        } else if (stringAttrs.find(attrName) != stringAttrs.end()) {
            // string
            attributes_[attrName] = { .type = ThemeConstantsType::STRING, .value = rawString };
        } else if (rawString.find(PATTERN_NAME_KEY_WORD) != std::string::npos) {
            // pattern
            auto patternStyle = AceType::MakeRefPtr<RawThemeStyle>(resAdapter_);
            patternStyle->SetName(attrName);
            patternStyle->parentStyle_ = AceType::WeakClaim(this);
            patternStyle->rawAttrs_ = attrValue->GetPattern();
            patternStyle->ParseContent();
            attributes_[attrName] = { .type = ThemeConstantsType::PATTERN,
                .value = RefPtr<ThemeStyle>(std::move(patternStyle)) };
        } else if (rawString.rfind(STATE_VALUE_KEY_WORD) != std::string::npos) {
            // state graphic value
            auto xmlWrapper = attrValue->GetLayoutValue();
            if (!xmlWrapper) {
                LOGW("Parse %{public}s state resource %{public}s error! xml is null!", name_.c_str(), attrName.c_str());
                continue;
            }
            auto stateResource = ParseStateResource(name_, attrName, std::move(xmlWrapper));
            if (!stateResource) {
                continue;
            }
            attributes_[attrName] = { .type = ThemeConstantsType::STATE_RESOURCE, .value = stateResource };
        } else if (rawString.find(REF_ATTR_VALUE_KEY_WORD) != std::string::npos) {
            attributes_[attrName] = { .type = ThemeConstantsType::REFERENCE_ATTR, .value = rawString };
        } else {
            // double & dimension
            std::string unit = "";
            auto doubleValue = static_cast<double>(attrValue->GetFloat(unit));
            if (unit.empty()) {
                attributes_[attrName] = { .type = ThemeConstantsType::DOUBLE, .value = doubleValue };
            } else {
                attributes_[attrName] = { .type = ThemeConstantsType::DIMENSION,
                    .value = Dimension(doubleValue, ParseDimensionUnit(unit)) };
            }
        }
    }
}

RefPtr<ResourceAdapter> ResourceAdapter::Create()
{
    auto deviceType = SystemProperties::GetDeviceType();
    if (deviceType == DeviceType::PHONE || deviceType == DeviceType::CAR || deviceType == DeviceType::TABLET ||
        deviceType == DeviceType::TWO_IN_ONE) {
        return AceType::MakeRefPtr<ResourceAdapterImpl>();
    }
    return RefPtr<ResourceAdapter>();
}

RefPtr<ResourceAdapter> ResourceAdapter::CreateNewResourceAdapter(
    const std::string& bundleName, const std::string& moduleName, int32_t& actualInstanceId)
{
    TAG_LOGW(AceLogTag::ACE_RESOURCE,
        "Cannot preview the component from the %{public}s module, because it contains a resource reference. Preview it "
        "in the %{public}s module instead.",
        moduleName.c_str(), moduleName.c_str());
    return nullptr;
}

ResourceAdapterImpl::ResourceAdapterImpl(std::shared_ptr<Global::Resource::ResourceManager> resourceManager)
{
    resourceManager_ = resourceManager;
    sysResourceManager_ = resourceManager;
}

void ResourceAdapterImpl::Init(const ResourceInfo& resourceInfo)
{
    std::vector<std::string> hapFiles;
    hapFiles.emplace_back(resourceInfo.GetPackagePath());
    auto configuration = ConvertConfig(resourceInfo.GetResourceConfiguration());
    auto handlers = resourceInfo.GetResourceHandlers();
    bool initRet = false;
    if (handlers.empty()) {
        initRet = resourceManger_.InitMock(hapFiles, resourceInfo.GetSystemPackagePath(), configuration);
    } else {
        initRet = resourceManger_.Init(hapFiles, handlers);
        resourceManger_.UpdateConfig(configuration);
    }
    LOGI("Init result=%{public}d, handle=%{public}zu, ori=%{public}d, dpi=%{public}f, device=%{public}d, "
         "font=%{public}f, color=%{public}d",
        initRet, handlers.size(), configuration.orientation_, configuration.resolution_, configuration.deviceType_,
        configuration.fontRatio_, configuration.colorMode_);
}

void ResourceAdapterImpl::UpdateConfig(const ResourceConfiguration& config, bool themeFlag)
{
    auto configuration = ConvertConfig(config);
    LOGI("UpdateConfig ori=%{public}d, dpi=%{public}f, device=%{public}d, font=%{public}f, color=%{public}d",
        configuration.orientation_, configuration.resolution_, configuration.deviceType_, configuration.fontRatio_,
        configuration.colorMode_);
    resourceManger_.UpdateConfig(configuration, themeFlag);
}

RefPtr<ThemeStyle> ResourceAdapterImpl::GetTheme(int32_t themeId)
{
    CheckThemeId(themeId);
    auto theme = AceType::MakeRefPtr<RawThemeStyle>(AceType::Claim(this));
    auto& attrMap = theme->rawAttrs_;
    auto ret = resourceManger_.GetTheme(themeId, attrMap);
    LOGI("GetTheme themeId=%{public}d, ret=%{public}d, attr size=%{public}zu", themeId, ret, attrMap.size());
    auto iter = attrMap.find(THEME_ATTR_BG_COLOR);
    if (iter != attrMap.end()) {
        auto& attribute = iter->second;
        if (attribute) {
            Color bgColor(attribute->GetColorValue());
            theme->SetAttr(THEME_ATTR_BG_COLOR, { .type = ThemeConstantsType::COLOR, .value = bgColor });
        }
    }
    return theme;
}

Color ResourceAdapterImpl::GetColor(uint32_t resId)
{
    uint32_t result = 0;
    auto ret = resourceManger_.GetColor(static_cast<int32_t>(resId), result);
    if (!ret) {
        LOGW("GetColor error, id=%{public}u", resId);
    }
    return Color(result);
}

Dimension ResourceAdapterImpl::GetDimension(uint32_t resId)
{
    float result = 0;
    std::string unit = "";
    auto ret = resourceManger_.GetFloat(static_cast<int32_t>(resId), result, unit);
    if (!ret) {
        LOGW("GetDimension error, id=%{public}u", resId);
    }
    return Dimension(result, ParseDimensionUnit(unit));
}

std::string ResourceAdapterImpl::GetString(uint32_t resId)
{
    std::string strResult = "";
    auto ret = resourceManger_.GetString(static_cast<int32_t>(resId), strResult);
    if (!ret) {
        LOGW("GetString error, id=%{public}u", resId);
    }
    return strResult;
}

std::string ResourceAdapterImpl::GetPluralString(uint32_t resId, int quantity)
{
    std::vector<std::string> pluralResults;
    auto ret = resourceManger_.GetStringArray(static_cast<int32_t>(resId), pluralResults);
    if (!ret) {
        LOGW("GetPluralString error, id=%{public}u", resId);
    }

    auto pluralChoice = Localization::GetInstance()->PluralRulesFormat(quantity);
    auto iter = std::find(pluralResults.begin(), pluralResults.end(), pluralChoice);
    std::string originStr;
    if (iter != pluralResults.end() && iter + 1 != pluralResults.end()) {
        iter++;
        originStr = *iter;
    }
    return originStr;
}

std::vector<std::string> ResourceAdapterImpl::GetStringArray(uint32_t resId) const
{
    std::vector<std::string> strResults;
    auto ret = resourceManger_.GetStringArray(static_cast<int32_t>(resId), strResults);
    if (!ret) {
        LOGW("GetStringArray error, id=%{public}u", resId);
    }
    return strResults;
}

double ResourceAdapterImpl::GetDouble(uint32_t resId)
{
    float result = 0.0f;
    auto ret = resourceManger_.GetFloat(static_cast<int32_t>(resId), result);
    if (!ret) {
        LOGW("GetDouble error, id=%{public}u", resId);
    }
    return static_cast<double>(result);
}

int32_t ResourceAdapterImpl::GetInt(uint32_t resId)
{
    int32_t result = 0;
    auto ret = resourceManger_.GetInt(static_cast<int32_t>(resId), result);
    if (!ret) {
        LOGW("GetInt error, id=%{public}u", resId);
    }
    return result;
}

bool ResourceAdapterImpl::GetResource(uint32_t resId, std::ostream& dest) const
{
    return resourceManger_.GetResource(static_cast<int32_t>(resId), dest);
}

bool ResourceAdapterImpl::GetResource(const std::string& path, std::ostream& dest) const
{
    return resourceManger_.GetResource(path, dest);
}

bool ResourceAdapterImpl::ConvertToGlobalResourceType(
    const std::string& resTypeName, Global::Resource::ResourceType& resType) const
{
    if (resTypeName == "color") {
        resType = Global::Resource::ResourceType::COLOR;
        return true;
    }
    if (resTypeName == "float") {
        resType = Global::Resource::ResourceType::FLOAT;
        return true;
    }
    if (resTypeName == "string") {
        resType = Global::Resource::ResourceType::STRING;
        return true;
    }
    if (resTypeName == "media") {
        resType = Global::Resource::ResourceType::MEDIA;
        return true;
    }
    LOGE("unsupported resource type(=%{public}s)", resTypeName.c_str());
    return false;
}

bool ResourceAdapterImpl::GetIdByName(const std::string& resName, const std::string& resType, uint32_t& resId) const
{
    Global::Resource::ResourceType globalResType;
    if (!ConvertToGlobalResourceType(resType, globalResType)) {
        return false;
    }
    int32_t globalResId = 0;
    if (!resourceManger_.GetIdByName("", resName, globalResType, globalResId)) {
        LOGW("get resource id failed.(name=%{public}s, type=%{public}s)", resName.c_str(), resType.c_str());
        return false;
    }
    resId = static_cast<uint32_t>(globalResId);
    return true;
}

std::string ResourceAdapterImpl::GetMediaPath(uint32_t resId)
{
    std::string mediaPath = "";
    auto ret = resourceManger_.GetString(static_cast<int32_t>(resId), mediaPath);
    if (!ret) {
        LOGW("GetMediaPath error, id=%{public}u", resId);
        return "";
    }
    return "resource://" + mediaPath.substr(0, mediaPath.find_last_of("/")) + "/" +
            std::to_string(resId) + mediaPath.substr(mediaPath.find_last_of("."));
}

std::string ResourceAdapterImpl::GetRawfile(const std::string& fileName)
{
    auto container = Container::Current();
    if (!container) {
        LOGW("container is null");
        return "";
    }
    auto moduleName = container->GetModuleName();
#if defined(PREVIEW)
    return "resource://RAWFILE/" + moduleName + "/resources/rawfile/" + fileName;
#else
    return "resource://RAWFILE/assets/" + moduleName + "/resources/rawfile/" + fileName;
#endif
}

uint32_t ResourceAdapterImpl::GetSymbolById(uint32_t resId) const
{
    uint32_t result = 0;
    resourceManger_->GetSymbolById(resId, result);
    return result;
}
} // namespace OHOS::Ace