/*
 *   Copyright (c) 2022 Project CHIP Authors
 *   All rights reserved.
 *
 *   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.
 *
 */

#pragma once

#include <string>

#include <app-common/zap-generated/cluster-objects.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteCommandPath.h>
#include <app/EventHeader.h>
#include <app/data-model/DecodableList.h>
#include <lib/support/BytesToHex.h>

class DataModelLogger
{
public:
    static CHIP_ERROR LogAttribute(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data);
    static CHIP_ERROR LogCommand(const chip::app::ConcreteCommandPath & path, chip::TLV::TLVReader * data);
    static CHIP_ERROR LogEvent(const chip::app::EventHeader & header, chip::TLV::TLVReader * data);

private:
    static CHIP_ERROR LogValue(const char * label, size_t indent, bool value)
    {
        DataModelLogger::LogString(label, indent, value ? "TRUE" : "FALSE");
        return CHIP_NO_ERROR;
    }

    static CHIP_ERROR LogValue(const char * label, size_t indent, chip::CharSpan value)
    {
        DataModelLogger::LogString(label, indent, std::string(value.data(), value.size()));
        return CHIP_NO_ERROR;
    }

    static CHIP_ERROR LogValue(const char * label, size_t indent, chip::ByteSpan value)
    {
        // CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE includes various prefixes we don't
        // control (timestamps, process ids, etc).  Let's assume (hope?) that
        // those prefixes use up no more than half the total available space.
        // Right now it looks like the prefixes are 45 chars out of a 255 char
        // buffer.
        char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE / 2];
        size_t prefixSize = ComputePrefixSize(label, indent);
        if (prefixSize > ArraySize(buffer))
        {
            DataModelLogger::LogString("", 0, "Prefix is too long to fit in buffer");
            return CHIP_ERROR_INTERNAL;
        }

        const size_t availableSize = ArraySize(buffer) - prefixSize;
        // Each byte ends up as two hex characters.
        const size_t bytesPerLogCall = availableSize / 2;
        std::string labelStr(label);
        while (value.size() > bytesPerLogCall)
        {
            ReturnErrorOnFailure(
                chip::Encoding::BytesToUppercaseHexString(value.data(), bytesPerLogCall, &buffer[0], ArraySize(buffer)));
            LogString(labelStr, indent, buffer);
            value = value.SubSpan(bytesPerLogCall);
            // For the second and following lines, make it clear that they are
            // continuation lines by replacing the label with "....".
            labelStr.replace(labelStr.begin(), labelStr.end(), labelStr.size(), '.');
        }
        ReturnErrorOnFailure(chip::Encoding::BytesToUppercaseHexString(value.data(), value.size(), &buffer[0], ArraySize(buffer)));
        LogString(labelStr, indent, buffer);

        return CHIP_NO_ERROR;
    }

    template <typename X,
              typename std::enable_if_t<
                  std::is_integral<X>::value && !std::is_same<std::remove_cv_t<std::remove_reference_t<X>>, bool>::value, int> = 0>
    static CHIP_ERROR LogValue(const char * label, size_t indent, X value)
    {
        DataModelLogger::LogString(label, indent, std::to_string(value));
        return CHIP_NO_ERROR;
    }

    template <typename X, typename std::enable_if_t<std::is_floating_point<X>::value, int> = 0>
    static CHIP_ERROR LogValue(const char * label, size_t indent, X value)
    {
        DataModelLogger::LogString(label, indent, std::to_string(value));
        return CHIP_NO_ERROR;
    }

    template <typename X, typename std::enable_if_t<std::is_enum<X>::value, int> = 0>
    static CHIP_ERROR LogValue(const char * label, size_t indent, X value)
    {
        DataModelLogger::LogValue(label, indent, chip::to_underlying(value));
        return CHIP_NO_ERROR;
    }

    template <typename X>
    static CHIP_ERROR LogValue(const char * label, size_t indent, chip::BitFlags<X> value)
    {
        DataModelLogger::LogValue(label, indent, value.Raw());
        return CHIP_NO_ERROR;
    }

    template <typename T>
    static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::app::DataModel::DecodableList<T> & value)
    {
        size_t count   = 0;
        CHIP_ERROR err = value.ComputeSize(&count);
        if (err != CHIP_NO_ERROR)
        {
            return err;
        }
        DataModelLogger::LogString(label, indent, std::to_string(count) + " entries");

        auto iter = value.begin();
        size_t i  = 0;
        while (iter.Next())
        {
            ++i;
            std::string itemLabel = std::string("[") + std::to_string(i) + "]";
            ReturnErrorOnFailure(DataModelLogger::LogValue(itemLabel.c_str(), indent + 1, iter.GetValue()));
        }
        if (iter.GetStatus() != CHIP_NO_ERROR)
        {
            DataModelLogger::LogString(indent + 1, "List truncated due to invalid value");
        }
        return iter.GetStatus();
    }

    template <typename T>
    static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::app::DataModel::Nullable<T> & value)
    {
        if (value.IsNull())
        {
            DataModelLogger::LogString(label, indent, "null");
        }
        else
        {
            DataModelLogger::LogValue(label, indent, value.Value());
        }

        return CHIP_NO_ERROR;
    }

    template <typename T>
    static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::Optional<T> & value)
    {
        if (value.HasValue())
        {
            DataModelLogger::LogValue(label, indent, value.Value());
        }

        return CHIP_NO_ERROR;
    }

#include <zap-generated/DataModelLogger.h>

    static void LogString(size_t indent, const std::string string) { LogString("", indent, string); }

    static void LogString(const std::string label, size_t indent, const std::string string)
    {
        std::string prefix = ComputePrefix(label, indent);

        ChipLogProgress(chipTool, "%s%s", prefix.c_str(), string.c_str());
    }

private:
    static std::string ComputePrefix(const std::string label, size_t indent)
    {
        std::string prefix;
        for (size_t i = 0; i < indent; ++i)
        {
            prefix.append("  ");
        }
        if (label.size() > 0)
        {
            prefix.append(label);
            prefix.append(":");
        }
        prefix.append(" ");

        return prefix;
    }

    static size_t ComputePrefixSize(const std::string label, size_t indent) { return ComputePrefix(label, indent).size(); }
};
