/*
 * Copyright (c) 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.
 */

#ifndef NWEB_VALUE_H
#define NWEB_VALUE_H

#include <iostream>
#include <map>
#include <set>
#include <memory>
#include <sstream>
#include <string>
#include <vector>

#include "nweb_export.h"

namespace OHOS::NWeb {

union data_union {
    int n;
    double f;
    bool b;

    data_union() {}

    data_union(int value) : n(value) {}

    data_union(double value) : f(value) {}

    data_union(bool value) : b(value) {}
};

class OHOS_NWEB_EXPORT NWebValue {
public:
    enum class Type : unsigned char {
        NONE = 0,
        BOOLEAN,
        INTEGER,
        DOUBLE,
        STRING,
        BINARY,
        DICTIONARY,
        LIST,
        ERROR,
        STRINGARRAY,
        BOOLEANARRAY,
        DOUBLEARRAY,
        INT64ARRAY
    };

    NWebValue() {}

    explicit NWebValue(Type type) : type_(type) {}

    explicit NWebValue(const int& value) : type_(Type::INTEGER), data_(value) {}

    explicit NWebValue(const double& value) : type_(Type::DOUBLE), data_(value) {}

    explicit NWebValue(const bool& value) : type_(Type::BOOLEAN), data_(value) {}

    explicit NWebValue(const std::string& value) : type_(Type::STRING), str_(value) {}

    NWebValue(const char* data, size_t len) : type_(Type::BINARY), str_(data, len) {}

    explicit NWebValue(const std::vector<NWebValue>& value) : type_(Type::LIST), list_value_(value.begin(), value.end())
    {}

    explicit NWebValue(const std::map<std::string, NWebValue>& value)
        : type_(Type::DICTIONARY), dictionary_value_(value)
    {}

    explicit NWebValue(const NWebValue& value) : type_(value.type_)
    {
        switch (type_) {
            case Type::NONE:
                break;
            case Type::BOOLEAN:
                data_.b = value.data_.b;
                break;
            case Type::INTEGER:
                data_.n = value.data_.n;
                break;
            case Type::DOUBLE:
                data_.f = value.data_.f;
                break;
            case Type::STRING:
                str_ = value.str_;
                break;
            case Type::BINARY:
                str_ = value.str_;
                break;
            case Type::LIST:
                list_value_ = value.list_value_;
                break;
            case Type::DICTIONARY:
                dictionary_value_ = value.dictionary_value_;
                break;
            default:
                break;
        }
    }

    NWebValue(std::vector<NWebValue>&& value) : type_(Type::LIST)
    {
        std::swap(list_value_, value);
    }

    NWebValue(std::map<std::string, NWebValue>&& value) : type_(Type::DICTIONARY)
    {
        std::swap(dictionary_value_, value);
    }

    NWebValue(NWebValue&& value)
    {
        *this = std::move(value);
    }

    ~NWebValue() = default;

    NWebValue& operator=(const NWebValue& value)
    {
        SetType(value.type_);
        switch (type_) {
            case Type::NONE:
                break;
            case Type::BOOLEAN:
                data_.b = value.data_.b;
                break;
            case Type::INTEGER:
                data_.n = value.data_.n;
                break;
            case Type::DOUBLE:
                data_.f = value.data_.f;
                break;
            case Type::STRING:
                str_ = value.str_;
                break;
            case Type::BINARY:
                str_ = value.str_;
                break;
            case Type::LIST:
                list_value_ = value.list_value_;
                break;
            case Type::DICTIONARY:
                dictionary_value_ = value.dictionary_value_;
                break;
            default:
                std::cout << "error: Invalid type" << std::endl;
                break;
        }
        return *this;
    }

    NWebValue& operator=(NWebValue&& value)
    {
        std::swap(type_, value.type_);
        switch (type_) {
            case Type::NONE:
                break;
            case Type::BOOLEAN:
                std::swap(data_.b, value.data_.b);
                break;
            case Type::INTEGER:
                std::swap(data_.n, value.data_.n);
                break;
            case Type::DOUBLE:
                std::swap(data_.f, value.data_.f);
                break;
            case Type::STRING:
                std::swap(str_, value.str_);
                break;
            case Type::BINARY:
                std::swap(str_, value.str_);
                break;
            case Type::LIST:
                std::swap(list_value_, value.list_value_);
                break;
            case Type::DICTIONARY:
                std::swap(dictionary_value_, value.dictionary_value_);
                break;
            default:
                std::cout << "error: Invalid type" << std::endl;
                break;
        }
        return *this;
    }

    bool operator==(NWebValue& oVal)
    {
        if (type_ != oVal.type_)
            return false;
        switch (type_) {
            case Type::NONE:
                return false;
            case Type::BOOLEAN:
                return data_.b == oVal.data_.b;
            case Type::INTEGER:
                return data_.n == oVal.data_.n;
            case Type::DOUBLE:
                return data_.f == oVal.data_.f;
            case Type::STRING:
                return str_ == oVal.str_;
            case Type::LIST:
                if ((*this).list_value_.size() != oVal.list_value_.size())
                    return false;
                for (size_t i = 0; i < list_value_.size(); ++i) {
                    NWebValue& lVal = oVal.list_value_[i];
                    NWebValue& rVal = (*this).list_value_[i];
                    if (!(lVal == rVal)) {
                        return false;
                    }
                }
                return true;
            case Type::DICTIONARY:
                if ((*this).dictionary_value_.size() != oVal.dictionary_value_.size())
                    return false;
                for (auto item : dictionary_value_) {
                    NWebValue& lVal = oVal.dictionary_value_[item.first];
                    NWebValue& rVal = (*this).dictionary_value_[item.first];
                    if (!(lVal == rVal)) {
                        return false;
                    }
                }
                return true;
            case Type::BINARY:
                return str_ == oVal.str_;
            default:
                std::cout << "error: Invalid type" << std::endl;
                return false;
        }
        return false;
    }

    bool IsNone()
    {
        return GetType() == Type::NONE;
    }

    bool IsBoolean()
    {
        return GetType() == Type::BOOLEAN;
    }

    bool IsString()
    {
        return GetType() == Type::STRING;
    }

    bool IsDouble()
    {
        return GetType() == Type::DOUBLE;
    }

    bool IsINTEGER()
    {
        return GetType() == Type::INTEGER;
    }

    bool IsList()
    {
        return GetType() == Type::LIST;
    }

    bool IsDictionary()
    {
        return GetType() == Type::DICTIONARY;
    }

    bool IsBinary()
    {
        return GetType() == Type::BINARY;
    }

    bool GetBoolean()
    {
        validateType(Type::BOOLEAN);
        return data_.b;
    }

    void SetBoolean(bool b)
    {
        validateType(Type::BOOLEAN);
        data_.b = b;
    }

    void SetString(std::string str)
    {
        validateType(Type::STRING);
        str_ = str;
    }

    std::string GetString()
    {
        validateType(Type::STRING);
        return str_;
    }

    void SetDouble(double dou)
    {
        validateType(Type::DOUBLE);
        data_.f = dou;
    }

    double GetDouble()
    {
        validateType(Type::DOUBLE);
        return data_.f;
    }

    void SetInt(int num)
    {
        validateType(Type::INTEGER);
        data_.n = num;
    }

    int GetInt()
    {
        validateType(Type::INTEGER);
        return data_.n;
    }

    size_t GetListValueSize()
    {
        validateType(Type::LIST);
        return list_value_.size();
    }

    std::vector<NWebValue> GetListValue()
    {
        validateType(Type::LIST);
        return list_value_;
    }

    NWebValue& GetListValue(unsigned int index)
    {
        validateType(Type::LIST);
        if (index >= list_value_.size()) {
            std::cout << "error: index larger than size()" << std::endl;
        }
        return list_value_[index];
    }

    void AddListValue(const NWebValue& value)
    {
        validateType(Type::LIST);
        SetType(Type::LIST);
        list_value_.push_back(value);
    }

    void deleteListValue()
    {
        validateType(Type::LIST);
        SetType(Type::LIST);
        list_value_.pop_back();
    }

    size_t GetDictionaryValueSize()
    {
        validateType(Type::DICTIONARY);
        return dictionary_value_.size();
    }

    std::vector<std::string> GetDictionaryValueKeys()
    {
        validateType(Type::DICTIONARY);
        std::vector<std::string> ret;
        for (auto& item : dictionary_value_) {
            ret.push_back(item.first);
        }
        return ret;
    }

    bool HasDictionaryValueKey(std::string& key)
    {
        validateType(Type::DICTIONARY);
        return dictionary_value_.count(key) == 1;
    }

    std::map<std::string, NWebValue> GetDictionaryValue()
    {
        validateType(Type::DICTIONARY);
        return dictionary_value_;
    }

    NWebValue& GetDictionaryValue(std::string& key)
    {
        validateType(Type::DICTIONARY);
        return dictionary_value_[key];
    }

    void AddDictionaryValue(std::string key, NWebValue& value)
    {
        validateType(Type::DICTIONARY);
        dictionary_value_[key] = value;
    }

    void DeleteDictionaryValue(std::string& key)
    {
        validateType(Type::DICTIONARY);
        dictionary_value_.erase(key);
    }

    size_t GetBinaryValueSize()
    {
        validateType(Type::BINARY);
        return str_.size();
    }

    const char* GetBinaryValue()
    {
        validateType(Type::BINARY);
        return (const char*)str_.c_str();
    }

    void SetJsonString(std::string json_string)
    {
        str_json_ = json_string;
    }

    std::string GetJsonString()
    {
        return str_json_;
    }

    Type GetType()
    {
        return type_;
    }

    void SetType(Type type)
    {
        type_ = type;
    }

    void validateType(Type type) const
    {
        if (type_ != Type::NONE && type_ != type) {
            std::cout << "error: Invalid type" << std::endl;
        }
    }

    int error_ = 0;

private:
    Type type_ = Type::NONE;
    data_union data_;
    std::string str_;
    std::string str_json_;
    std::map<std::string, NWebValue> dictionary_value_;
    std::vector<NWebValue> list_value_;
};

} // namespace OHOS::NWeb

#endif // NWEB_VALUE_H