//
// Configuration parser 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 "cfgparser.hh"
#include <type_traits>
#include <charconv>
#include <stdexcept>
#include <fstream>
#include <array>

template <typename P>
bool CfgParameter<P>::parse(std::string_view line)
{
  // Empty lines are ignored
  if (line.empty())
    return true;

  // Comments are ignored
  if (line[0] == '#')
    return true;

  // Must be a declaration then, see if we match by our name
  if (!line.starts_with(m_name))
    return false;
  line.remove_prefix(m_name.length());

  // Our name matched, now see if an assignment (or space) follows
  bool got_assignment = false;
  while (line.starts_with(" ")
         || line.starts_with("=")) {
    got_assignment |= line[0] == '=';
    line.remove_prefix(1);
  }
  if (!got_assignment)
    return false;

  //
  // We're at the value then - parse
  //
  if constexpr(std::is_same_v<P, std::string>) {
    // Quoted string - get start quote
    char quote;
    if (!line.starts_with('"') && !line.starts_with('\''))
      throw std::runtime_error("String missing start quote");
    quote = line[0];
    line.remove_prefix(1);

    // Get everything in between
    m_value = line.substr(0, line.find(quote));

    // Test that string ends with end quote
    if (!line.ends_with(quote))
      throw std::runtime_error("Quoted string missing end quote");

    // Success then
    return true;
  } else if constexpr (std::is_integral_v<P> || std::is_floating_point_v<P>){
    // Integer or float
    auto line_end = line.data() + line.size();
    auto [ptr, ec] = std::from_chars(line.data(), line_end, m_value);
    if (ec == std::errc()) {
      // If we parsed the full line we're good - otherwise it's a
      // syntax error
      if (ptr != line_end)
        throw std::runtime_error("Trailing data on line");
      return true;
    } else
      throw std::runtime_error("Error parsing number");
  } else {
    // Unsupported type
    static_assert(false);
  }

  return false;
}

//
// Explicitly instantiate for needed types
//
template class CfgParameter<size_t>;
template class CfgParameter<int32_t>;
template class CfgParameter<std::string>;
template class CfgParameter<double>;


void CfgParser::reload()
{
  std::ifstream infile(m_filename);
  if (!infile.is_open())
    throw std::runtime_error("Unable to open configuration file");

  // Read lines from file one by one
  std::string linebuf;
  while (infile.rdstate() == std::ios_base::goodbit) {
    // Read a line; stop if at EOF
    std::getline(infile, linebuf);

    // We got a line - now run through parameters until it parses
    for (auto p: m_parms)
      if (p->parse(linebuf))
        goto success;

    // End of parameters but still no parse
    if (!m_ignore_unknown_parameters)
      throw std::runtime_error("Unable to parse line: '"
                               + linebuf + "'");
    // Successful parse; move to parse next line
  success: {}
  }

  // Done
  if (!(infile.rdstate() & std::ios_base::eofbit))
    throw std::runtime_error("Error during parsing of config file");
}

void CfgParser::ignore_unknown_parameters()
{
  m_ignore_unknown_parameters = true;
}
