/**
 * Copyright 2017 Google Inc.
 *
 * 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"

#include "fancontroller.hpp"

#include "tuning.hpp"
#include "util.hpp"
#include "zone.hpp"

#include <algorithm>
#include <cmath>
#include <iostream>

namespace pid_control
{

std::unique_ptr<PIDController> FanController::createFanPid(
    ZoneInterface* owner, const std::string& id,
    const std::vector<std::string>& inputs, const ec::pidinfo& initial)
{
    if (inputs.size() == 0)
    {
        return nullptr;
    }
    auto fan = std::make_unique<FanController>(id, inputs, owner);
    ec::pid_info_t* info = fan->getPIDInfo();

    initializePIDStruct(info, initial);

    return fan;
}

double FanController::inputProc(void)
{
    double value = 0.0;
    std::vector<double> values;
    std::vector<double>::iterator result;

    try
    {
        for (const auto& name : _inputs)
        {
            // Read the unscaled value, to correctly recover the RPM
            value = _owner->getCachedValues(name).unscaled;

            /* If we have a fan we can't read, its value will be 0 for at least
             * some boards, while others... the fan will drop off dbus (if
             * that's how it's being read and in that case its value will never
             * be updated anymore, which is relatively harmless, except, when
             * something tries to read its value through IPMI, and can't, they
             * sort of have to guess -- all the other fans are reporting, why
             * not this one?  Maybe it's unable to be read, so it's "bad."
             */
            if (!(std::isfinite(value)))
            {
                continue;
            }
            if (value <= 0.0)
            {
                continue;
            }

            values.push_back(value);
        }
    }
    catch (const std::exception& e)
    {
        std::cerr << "exception on inputProc.\n";
        throw;
    }

    /* Reset the value from the above loop. */
    value = 0.0;
    if (values.size() > 0)
    {
        /* the fan PID algorithm was unstable with average, and seemed to work
         * better with minimum.  I had considered making this choice a variable
         * in the configuration, and it's a nice-to-have..
         */
        result = std::min_element(values.begin(), values.end());
        value = *result;
    }

    return value;
}

double FanController::setptProc(void)
{
    double maxRPM = _owner->getMaxSetPointRequest();

    // store for reference, and check if more or less.
    double prev = getSetpoint();

    if (maxRPM > prev)
    {
        setFanDirection(FanSpeedDirection::UP);
    }
    else if (prev > maxRPM)
    {
        setFanDirection(FanSpeedDirection::DOWN);
    }
    else
    {
        setFanDirection(FanSpeedDirection::NEUTRAL);
    }

    setSetpoint(maxRPM);

    return (maxRPM);
}

void FanController::outputProc(double value)
{
    double percent = value;

    /* If doing tuning, don't go into failsafe mode. */
    if (!tuningEnabled)
    {
        bool failsafeCurrState = _owner->getFailSafeMode();

        // Note when failsafe state transitions happen
        if (failsafePrevState != failsafeCurrState)
        {
            failsafePrevState = failsafeCurrState;
            failsafeTransition = true;
        }

        if (failsafeCurrState)
        {
            double failsafePercent = _owner->getFailSafePercent();

#ifdef STRICT_FAILSAFE_PWM
            // Unconditionally replace the computed PWM with the
            // failsafe PWM if STRICT_FAILSAFE_PWM is defined.
            percent = failsafePercent;
#else
            // Ensure PWM is never lower than the failsafe PWM.
            // The computed PWM is still allowed to rise higher than
            // failsafe PWM if STRICT_FAILSAFE_PWM is NOT defined.
            // This is the default behavior.
            if (percent < failsafePercent)
            {
                percent = failsafePercent;
            }
#endif
        }

        // Always print if debug enabled
        if (debugEnabled)
        {
            std::cerr << "Zone " << _owner->getZoneID() << " fans, "
                      << (failsafeCurrState ? "failsafe" : "normal")
                      << " mode, output pwm: " << percent << "\n";
        }
        else
        {
            // Only print once per transition when not debugging
            if (failsafeTransition)
            {
                failsafeTransition = false;
                std::cerr << "Zone " << _owner->getZoneID() << " fans, "
                          << (failsafeCurrState ? "entering failsafe"
                                                : "returning to normal")
                          << " mode, output pwm: " << percent << "\n";
            }
        }
    }
    else
    {
        if (debugEnabled)
        {
            std::cerr << "Zone " << _owner->getZoneID()
                      << " fans, tuning mode, bypassing failsafe, output pwm: "
                      << percent << "\n";
        }
    }

    // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
    percent /= 100.0;

    // PidSensorMap for writing.
    for (const auto& it : _inputs)
    {
        auto sensor = _owner->getSensor(it);
        auto redundantWrite = _owner->getRedundantWrite();
        int64_t rawWritten = -1;
        sensor->write(percent, redundantWrite, &rawWritten);

        // The outputCache will be used later,
        // to store a record of the PWM commanded,
        // so that this information can be included during logging.
        auto unscaledWritten = static_cast<double>(rawWritten);
        _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
    }

    return;
}

FanController::~FanController()
{
#ifdef OFFLINE_FAILSAFE_PWM
    double percent = _owner->getFailSafePercent();
    if (debugEnabled)
    {
        std::cerr << "Zone " << _owner->getZoneID()
                  << " offline fans output pwm: " << percent << "\n";
    }

    // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
    percent /= 100.0;

    // PidSensorMap for writing.
    for (const auto& it : _inputs)
    {
        auto sensor = _owner->getSensor(it);
        auto redundantWrite = _owner->getRedundantWrite();
        int64_t rawWritten;
        sensor->write(percent, redundantWrite, &rawWritten);

        // The outputCache will be used later,
        // to store a record of the PWM commanded,
        // so that this information can be included during logging.
        auto unscaledWritten = static_cast<double>(rawWritten);
        _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
    }
#endif
}

} // namespace pid_control