/**
 * Copyright © 2021 IBM Corporation
 *
 * 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 "modifier.hpp"

#include "json/manager.hpp"

#include <fmt/format.h>

#include <phosphor-logging/log.hpp>

using namespace phosphor::logging;

namespace phosphor::fan::control::json
{

/**
 * @brief Variant visitor to return a value of the template type specified.
 */
template <typename T>
struct ToTypeVisitor
{
    template <typename U>
    T operator()(const U& t) const
    {
        if constexpr (std::is_arithmetic_v<U> && std::is_arithmetic_v<T>)
        {
            return static_cast<T>(t);
        }
        throw std::invalid_argument(
            "Non arithmetic type used in ToTypeVisitor");
    }
};

/**
 * @brief Implements the minus operator to subtract two values.
 *
 * With strings values, A - B removes all occurrences of B in A.
 * Throws if the type is a bool.
 */
struct MinusOperator : public Modifier::BaseOperator
{
    PropertyVariantType operator()(double val) override
    {
        return val - std::visit(ToTypeVisitor<double>(), arg);
    }

    PropertyVariantType operator()(int32_t val) override
    {
        return val - std::visit(ToTypeVisitor<int32_t>(), arg);
    }

    PropertyVariantType operator()(int64_t val) override
    {
        return val - std::visit(ToTypeVisitor<int64_t>(), arg);
    }

    PropertyVariantType operator()(const std::string& val) override
    {
        // Remove all occurrences of arg from val.
        auto value = val;
        auto toRemove = std::get<std::string>(arg);
        size_t pos;
        while ((pos = value.find(toRemove)) != std::string::npos)
        {
            value.erase(pos, toRemove.size());
        }

        return value;
    }

    PropertyVariantType operator()(bool val) override
    {
        throw std::runtime_error{
            "Bool not allowed as a 'minus' modifier value"};
    }

    MinusOperator(PropertyVariantType& arg) : arg(arg)
    {}

    PropertyVariantType arg;
};

Modifier::Modifier(const json& jsonObj)
{
    setValue(jsonObj);
    setOperator(jsonObj);
}

void Modifier::setValue(const json& jsonObj)
{
    if (!jsonObj.contains("value"))
    {
        log<level::ERR>(
            fmt::format("Modifier entry in JSON missing 'value': {}",
                        jsonObj.dump())
                .c_str());
        throw std::invalid_argument("Invalid modifier JSON");
    }

    const auto& object = jsonObj.at("value");
    if (auto boolPtr = object.get_ptr<const bool*>())
    {
        _value = *boolPtr;
    }
    else if (auto intPtr = object.get_ptr<const int64_t*>())
    {
        _value = *intPtr;
    }
    else if (auto doublePtr = object.get_ptr<const double*>())
    {
        _value = *doublePtr;
    }
    else if (auto stringPtr = object.get_ptr<const std::string*>())
    {
        _value = *stringPtr;
    }
    else
    {
        log<level::ERR>(
            fmt::format(
                "Invalid JSON type for value property in modifer json: {}",
                jsonObj.dump())
                .c_str());
        throw std::invalid_argument("Invalid modifier JSON");
    }
}

void Modifier::setOperator(const json& jsonObj)
{
    if (!jsonObj.contains("operator"))
    {
        log<level::ERR>(
            fmt::format("Modifier entry in JSON missing 'operator': {}",
                        jsonObj.dump())
                .c_str());
        throw std::invalid_argument("Invalid modifier JSON");
    }

    auto op = jsonObj["operator"].get<std::string>();

    if (op == "minus")
    {
        _operator = std::make_unique<MinusOperator>(_value);
    }
    else
    {
        log<level::ERR>(fmt::format("Invalid operator in the modifier JSON: {}",
                                    jsonObj.dump())
                            .c_str());
        throw std::invalid_argument("Invalid operator in the modifier JSON");
    }
}

PropertyVariantType Modifier::doOp(const PropertyVariantType& val)
{
    return std::visit(*_operator, val);
}

} // namespace phosphor::fan::control::json