/*
// Copyright (c) 2019 Intel 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.
*/
#pragma once
#include <fcntl.h>
#include <unistd.h>

#include <cereal/access.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/cereal.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/tuple.hpp>
#include <cereal/types/vector.hpp>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <phosphor-logging/elog-errors.hpp>
#include <xyz/openbmc_project/Collection/DeleteAll/server.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <xyz/openbmc_project/State/Boot/PostCode/server.hpp>
#include <xyz/openbmc_project/State/Host/server.hpp>

const static constexpr char *CurrentBootCycleCountName =
    "CurrentBootCycleCount";
const static constexpr char *CurrentBootCycleIndexName =
    "CurrentBootCycleIndex";

// Singleton holder to store host/node and other path information
class PostCodeDataHolder
{
    static PostCodeDataHolder *instance;

    PostCodeDataHolder()
    {
    }

  public:
    static PostCodeDataHolder *getInstance()
    {
        if (!instance)
            instance = new PostCodeDataHolder;
        return instance;
    }

    int node;

    const static constexpr char *PostCodePath =
        "/xyz/openbmc_project/state/boot/raw";
    const static constexpr char *PropertiesIntf =
        "org.freedesktop.DBus.Properties";
    const static constexpr char *PostCodeListPathPrefix =
        "/var/lib/phosphor-post-code-manager/host";
    const static constexpr char *HostStatePathPrefix =
        "/xyz/openbmc_project/state/host";
};

struct EventDeleter
{
    void operator()(sd_event *event) const
    {
        event = sd_event_unref(event);
    }
};
using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
using primarycode_t = uint64_t;
using secondarycode_t = std::vector<uint8_t>;
using postcode_t = std::tuple<primarycode_t, secondarycode_t>;
namespace fs = std::filesystem;
namespace StateServer = sdbusplus::xyz::openbmc_project::State::server;

using post_code =
    sdbusplus::xyz::openbmc_project::State::Boot::server::PostCode;
using delete_all =
    sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll;

struct PostCode : sdbusplus::server::object_t<post_code, delete_all>
{
    PostCodeDataHolder *postcodeDataHolderObj =
        postcodeDataHolderObj->getInstance();

    PostCode(sdbusplus::bus::bus &bus, const char *path, EventPtr & /*event*/) :
        sdbusplus::server::object_t<post_code, delete_all>(bus, path), bus(bus),
        propertiesChangedSignalRaw(
            bus,
            sdbusplus::bus::match::rules::type::signal() +
                sdbusplus::bus::match::rules::member("PropertiesChanged") +
                sdbusplus::bus::match::rules::path(
                    postcodeDataHolderObj->PostCodePath +
                    std::to_string(postcodeDataHolderObj->node)) +
                sdbusplus::bus::match::rules::interface(
                    postcodeDataHolderObj->PropertiesIntf),
            [this](sdbusplus::message::message &msg) {
                std::string objectName;
                std::map<std::string, std::variant<postcode_t>> msgData;
                msg.read(objectName, msgData);
                // Check if it was the Value property that changed.
                auto valPropMap = msgData.find("Value");
                {
                    if (valPropMap != msgData.end())
                    {
                        this->savePostCodes(
                            std::get<postcode_t>(valPropMap->second));
                    }
                }
            }),
        propertiesChangedSignalCurrentHostState(
            bus,
            sdbusplus::bus::match::rules::type::signal() +
                sdbusplus::bus::match::rules::member("PropertiesChanged") +
                sdbusplus::bus::match::rules::path(
                    postcodeDataHolderObj->HostStatePathPrefix +
                    std::to_string(postcodeDataHolderObj->node)) +
                sdbusplus::bus::match::rules::interface(
                    postcodeDataHolderObj->PropertiesIntf),
            [this](sdbusplus::message::message &msg) {
                std::string objectName;
                std::map<std::string, std::variant<std::string>> msgData;
                msg.read(objectName, msgData);
                // Check if it was the Value property that changed.
                auto valPropMap = msgData.find("CurrentHostState");
                {
                    if (valPropMap != msgData.end())
                    {
                        StateServer::Host::HostState currentHostState =
                            StateServer::Host::convertHostStateFromString(
                                std::get<std::string>(valPropMap->second));
                        if (currentHostState ==
                            StateServer::Host::HostState::Off)
                        {
                            if (this->postCodes.empty())
                            {
                                std::cerr << "HostState changed to OFF. Empty "
                                             "postcode log, keep boot cycle at "
                                          << this->currentBootCycleIndex
                                          << std::endl;
                            }
                            else
                            {
                                this->postCodes.clear();
                            }
                        }
                    }
                }
            })
    {
        phosphor::logging::log<phosphor::logging::level::INFO>(
            "PostCode is created");
        auto dir = fs::path(postcodeDataHolderObj->PostCodeListPathPrefix +
                            std::to_string(postcodeDataHolderObj->node));
        fs::create_directories(dir);
        strPostCodeListPath = postcodeDataHolderObj->PostCodeListPathPrefix +
                              std::to_string(postcodeDataHolderObj->node) + "/";
        strCurrentBootCycleIndexName = CurrentBootCycleIndexName;
        uint16_t index = 0;
        deserialize(
            fs::path(strPostCodeListPath + strCurrentBootCycleIndexName),
            index);
        currentBootCycleIndex = index;
        strCurrentBootCycleCountName = CurrentBootCycleCountName;
        uint16_t count = 0;
        deserialize(
            fs::path(strPostCodeListPath + strCurrentBootCycleCountName),
            count);
        currentBootCycleCount(count);
        maxBootCycleNum(MAX_BOOT_CYCLE_COUNT);
    }
    ~PostCode()
    {
    }

    std::vector<postcode_t> getPostCodes(uint16_t index) override;
    std::map<uint64_t, postcode_t>
        getPostCodesWithTimeStamp(uint16_t index) override;
    void deleteAll() override;

  private:
    void incrBootCycle();
    uint16_t getBootNum(const uint16_t index) const;

    sdbusplus::bus::bus &bus;
    std::chrono::time_point<std::chrono::steady_clock> firstPostCodeTimeSteady;
    uint64_t firstPostCodeUsSinceEpoch;
    std::map<uint64_t, postcode_t> postCodes;
    std::string strPostCodeListPath;
    std::string strCurrentBootCycleIndexName;
    uint16_t currentBootCycleIndex;
    std::string strCurrentBootCycleCountName;
    void savePostCodes(postcode_t code);
    sdbusplus::bus::match_t propertiesChangedSignalRaw;
    sdbusplus::bus::match_t propertiesChangedSignalCurrentHostState;
    fs::path serialize(const std::string &path);
    bool deserialize(const fs::path &path, uint16_t &index);
    bool deserializePostCodes(const fs::path &path,
                              std::map<uint64_t, postcode_t> &codes);
};