/**
 * Copyright © 2022 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 "config.h"

#ifndef CONTROL_USE_JSON
#include "manager.hpp"

#include <CLI/CLI.hpp>
#else
#include "../utils/flight_recorder.hpp"
#include "json/manager.hpp"
#endif

#include "dbus_paths.hpp"
#include "sdbusplus.hpp"
#include "sdeventplus.hpp"

#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
#include <sdeventplus/event.hpp>
#include <sdeventplus/source/signal.hpp>
#include <stdplus/signal.hpp>

#include <fstream>

using namespace phosphor::fan::control;
using namespace phosphor::logging;

#ifdef CONTROL_USE_JSON
void dumpFlightRecorder()
{
    nlohmann::json data;
    phosphor::fan::control::json::FlightRecorder::instance().dump(data);
    std::ofstream file{json::Manager::dumpFile};
    file << std::setw(4) << data;
}
#endif

int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
    auto event = phosphor::fan::util::SDEventPlus::getEvent();

#ifndef CONTROL_USE_JSON
    CLI::App app{"Phosphor Fan Control"};

    bool init = false;
    bool control = false;
    app.add_flag("-i,--init", init, "Sets fans to full speed, delays, exits");
    app.add_flag("-c,--control", control, "Start fan control algorithm");
    app.require_option();

    try
    {
        app.parse(argc, argv);
    }
    catch (const CLI::Error& e)
    {
        return app.exit(e);
    }

    Mode mode;
    if (init)
    {
        mode = Mode::init;
    }
    else if (control)
    {
        mode = Mode::control;
    }
#endif

    // Attach the event object to the bus object so we can
    // handle both sd_events (for the timers) and dbus signals.
    phosphor::fan::util::SDBusPlus::getBus().attach_event(
        event.get(), SD_EVENT_PRIORITY_NORMAL);

    try
    {
#ifdef CONTROL_USE_JSON
        phosphor::fan::control::json::FlightRecorder::instance().log("main",
                                                                     "Startup");
        json::Manager manager(event);

        // Handle loading fan control's config file(s)
        phosphor::fan::JsonConfig config(
            std::bind(&json::Manager::load, &manager));

        // Enable SIGHUP handling to reload JSON configs
        stdplus::signal::block(SIGHUP);
        sdeventplus::source::Signal signal(
            event, SIGHUP,
            std::bind(&json::Manager::sighupHandler, &manager,
                      std::placeholders::_1, std::placeholders::_2));

        // Enable SIGUSR1 handling to dump the flight recorder
        stdplus::signal::block(SIGUSR1);
        sdeventplus::source::Signal sigUsr1(
            event, SIGUSR1,
            std::bind(&json::Manager::dumpDebugData, &manager,
                      std::placeholders::_1, std::placeholders::_2));

        phosphor::fan::util::SDBusPlus::getBus().request_name(CONTROL_BUSNAME);
#else
        Manager manager(phosphor::fan::util::SDBusPlus::getBus(), event, mode);

        // Init mode will just set fans to max and delay
        if (mode == Mode::init)
        {
            manager.doInit(event);
            return 0;
        }
#endif
        return event.loop();
    }
    // Log the useful metadata on these exceptions and let the app
    // return 1 so it is restarted without a core dump.
    catch (const phosphor::fan::util::DBusServiceError& e)
    {
        log<level::ERR>("Uncaught DBus service lookup failure exception",
                        entry("PATH=%s", e.path.c_str()),
                        entry("INTERFACE=%s", e.interface.c_str()));
    }
    catch (const phosphor::fan::util::DBusMethodError& e)
    {
        log<level::ERR>("Uncaught DBus method failure exception",
                        entry("BUSNAME=%s", e.busName.c_str()),
                        entry("PATH=%s", e.path.c_str()),
                        entry("INTERFACE=%s", e.interface.c_str()),
                        entry("METHOD=%s", e.method.c_str()));
    }
    catch (const phosphor::fan::util::DBusPropertyError& e)
    {
        log<level::ERR>("Uncaught DBus property access failure exception",
                        entry("BUSNAME=%s", e.busName.c_str()),
                        entry("PATH=%s", e.path.c_str()),
                        entry("INTERFACE=%s", e.interface.c_str()),
                        entry("PROPERTY=%s", e.property.c_str()));
    }
    catch (std::exception& e)
    {
#ifdef CONTROL_USE_JSON
        phosphor::fan::control::json::FlightRecorder::instance().log(
            "main", "Unexpected exception exit");
        dumpFlightRecorder();
#endif
        throw;
    }

#ifdef CONTROL_USE_JSON
    phosphor::fan::control::json::FlightRecorder::instance().log(
        "main", "Abnormal exit");
    dumpFlightRecorder();
#endif

    return 1;
}