//
// PWM implementation
//
// Copyright 2026, Jakob Østergaard Hegelund <joe@unthought.net>
// -----------------------------------------------------------------------
// This file is part of FanMan.
//
// FanMan is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FanMan is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FanMan. If not, see <https://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------
//

#include "pwm.hh"
#include <fstream>
#include <sstream>
#include <cmath>
#include <algorithm>

PWMChannel::PWMChannel(size_t chip, size_t channel, size_t period)
  : m_chip(chip), m_channel(channel), m_period(period)
{
  // See if we can locate the PWM at all
  { std::ifstream enable(channel_file("enable"));
    if (enable.rdstate() != std::ios_base::goodbit) {
      // Unable to locate PWM file - enable the PWM first
      std::ofstream fexport(chip_file("export"));
      if (fexport.rdstate() != std::ios_base::goodbit)
        throw std::runtime_error("Cannot enable PWM channel");
      // Export the given PWM channel
      fexport << channel;
    }
  }

  // Disable the PWM if it is already enabled
  { std::ofstream wenable(channel_file("enable"));
    if (wenable.rdstate() != std::ios_base::goodbit)
      throw std::runtime_error("PWM channel not exported");
    wenable << channel;
  }

  // See if we are "inversed"
  { std::ifstream polarity(channel_file("polarity"));
    if (polarity.rdstate() != std::ios_base::goodbit)
      throw std::runtime_error("Cannot determine polarity");
    std::string pol;
    polarity >> pol;
    m_inversed = pol == "inversed";
  }
}


std::string PWMChannel::chip_prefix()
{
  std::ostringstream str;
  str << "/sys/class/pwm/pwmchip" << m_chip;
  return str.str();
}

std::string PWMChannel::channel_prefix()
{
  std::ostringstream str;
  str << "/sys/class/pwm/pwmchip" << m_chip
      << "/pwm" << m_channel;
  return str.str();
}

std::string PWMChannel::chip_file(std::string f)
{
  return chip_prefix() + "/" + f;
}

std::string PWMChannel::channel_file(std::string f)
{
  return channel_prefix() + "/" + f;
}

void PWMChannel::set_dc(double dc)
{
  // Argument capping - just to be sane
  if (dc < 0) dc = 0;
  if (dc > 1) dc = 1;
  write_channel_file("duty_cycle",
                     std::lround((m_inversed ? (1-dc) : dc) * m_period));
}

void PWMChannel::enable(bool e)
{
  write_channel_file("period", m_period);
  set_dc(0);
  write_channel_file("enable", e ? 1 : 0);
}

void PWMChannel::write_channel_file(std::string f, size_t u)
{
  if (f != m_last_fname) {
    const std::string cfname = channel_file(f);
    if (m_last_file.is_open())
      m_last_file.close();
    m_last_file.open(cfname);
    m_last_fname = f;
  }
  if (m_last_file.rdstate() != std::ios_base::goodbit)
    throw std::runtime_error("Cannot open channel file " + f);
  m_last_file << u;
  m_last_file.seekp(0);
}
