// Copyright (c) 2016, 2017 GeometryFactory
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.2/CGAL_ImageIO/include/CGAL/SEP_header.h $
// $Id: include/CGAL/SEP_header.h 37e2b278910 $
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Laurent Rineau

#ifndef CGAL_SEP_HEADER_HPP
#define CGAL_SEP_HEADER_HPP

#include <string>
#include <map>
#include <fstream>
#include <iostream>
#include <algorithm>

#include <boost/tuple/tuple.hpp>
#include <boost/fusion/adapted/boost_tuple.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/phoenix/core.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/phoenix/object.hpp>
#include <boost/phoenix/stl.hpp>
#include <boost/phoenix/fusion.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

#ifdef CGAL_SEP_READER_DEBUG
#  include <boost/tuple/tuple_io.hpp>
// The debug the Boost.Spirit.Qi parser, this is needed:
namespace std {
template <typename A, typename B>
inline std::ostream& operator<<(std::ostream& out, const std::pair<A, B>& id) {
  return out << id.first << " " << id.second;
}
} //end namespace std
#endif // CGAL_SEP_READER_DEBUG

namespace CGAL {

struct SEP_header_aux
{
  typedef std::map<std::string, int> Int_dict;
  typedef std::map<std::string, double> Double_dict;
  typedef std::map<std::string, std::string> String_dict;
  Int_dict int_dict;
  Double_dict double_dict;
  String_dict string_dict;

public:
  template <typename Tuple_string_variant>
  SEP_header_aux& operator<<(const Tuple_string_variant& tuple)
  {
    using boost::get;
    visitor vis(this, get<0>(tuple));
    std::visit(vis, get<1>(tuple));
    return *this;
  }

private:
  struct visitor {
    SEP_header_aux* self;
    std::string key;
    visitor(SEP_header_aux* header, std::string key)
      : self(header)
      , key(key)
    {}

    template <typename T>
    void operator()(const T& t) {
      // std::cerr << "My assignment ("
      //           << typeid(t).name() << "): "
      //           << key << "=" << t << std::endl;
      self->add(key, t);
    }
  };

  void add(const std::string& key, int v) {
    int_dict[key] = v;
    double_dict[key] = v;
  }

  void add(const std::string& key, const double& v) {
    double_dict[key] = v;
  }

  void add(const std::string& key, const std::string& v) {
    string_dict[key] = v;
  }
}; // end struct SEP_header_aux

} // end namespace CGAL

BOOST_FUSION_ADAPT_STRUCT(
    CGAL::SEP_header_aux,
    ( CGAL::SEP_header_aux::Int_dict,     int_dict)
    ( CGAL::SEP_header_aux::Double_dict,  double_dict)
    ( CGAL::SEP_header_aux::String_dict,  string_dict)
)

namespace CGAL {

class SEP_header {

  std::array<std::size_t, 3> _n;
  std::array<double, 3> _d;
  std::array<double, 3> _o;

  SEP_header_aux::String_dict _string_dict;

  int _dim;

public:
  /// constructor
  SEP_header(std::string fileName) : _dim(-1) {
    std::ifstream input(fileName.c_str());
    if(!input) {
      std::cerr << "Error: cannot open the header file \""
                << fileName << "\"!\n";
      return;
    }
    SEP_header_aux header;
    if(parseHeader(input, header))
    {
      get_n(1) = header.int_dict["n1"];
      get_n(2) = header.int_dict["n2"];
      get_n(3) = header.int_dict["n3"];
      _dim = 0;
      for(int i = 1; i <= 3; ++i) {
        if(get_n(i) == 0) get_n(i) = 1;
        if(get_n(i) != 1) ++_dim;
      }
      get_d(1) = header.double_dict["d1"];
      get_d(2) = header.double_dict["d2"];
      get_d(3) = header.double_dict["d3"];
      get_o(1) = header.double_dict["o1"];
      get_o(2) = header.double_dict["o2"];
      get_o(3) = header.double_dict["o3"];
      _string_dict = header.string_dict;
    }
  }

  /// copy-constructor
  SEP_header(const SEP_header& other)
    : _n(other._n)
    , _d(other._d)
    , _o(other._o)
    , _dim(other._dim)
  {}

  /// const getters
  /// @{
  double o(int i) const { return _o[i-1]; }
  double d(int i) const { return _d[i-1]; }
  std::size_t n(int i) const { return _n[i-1]; }
  int dimension() const { return _dim;}
  std::string string_field(std::string key) const {
    SEP_header_aux::String_dict::const_iterator it = _string_dict.find(key);
    if(it == _string_dict.end()) return std::string();
    else return it->second;
  }
  /// @}

  /// non-const getters
  /// @{
  double& get_o(int i) { return _o[i-1]; }
  double& get_d(int i) { return _d[i-1]; }
  std::size_t& get_n(int i) { return _n[i-1]; }
  int& get_dim() { return _dim; }
  /// @}

  std::ostream& display_information(std::string fileName,
                                   std::ostream& out) const {
    out << "Header file: " << fileName << std::endl;
    out << "Parameters:\n";
    out << "dimension = " << dimension() << "\n";
    for(int i = 1; i <= 3 ; ++i) {
      out << "d" << i << " = " << d(i) << "\n";
      out << "n" << i << " = " << n(i) << "\n";
      out << "o" << i << " = " << o(i) << "\n";
    }
    out << "in = " << string_field("in") << std::endl;
    return out;
  }

private:
  template <typename Iterator>
  struct sep_header_grammar : boost::spirit::qi::grammar<Iterator, SEP_header_aux()>
  {
    sep_header_grammar()
      : sep_header_grammar::base_type(header,
                                      "sep_header_grammar")
    {
      namespace qi = boost::spirit::qi;
      namespace ascii = boost::spirit::ascii;
      using qi::lexeme;
      using ascii::char_;
      using qi::int_;
      using qi::real_parser;
      using boost::phoenix::at_c;
      using qi::_1;
      using qi::_2;
      using qi::_val;

      qi::real_parser<double,
                      qi::strict_real_policies<double> > double_with_dot;

      skip_comment = ('#' >> *(qi::char_ - qi::eol) >> qi::eol);
      skip_comment.name("my skip comment");

      skip = skip_comment | qi::space;
      skip.name("my skipper");

      identifier = +((qi::alnum|char_('_'))[_val += _1]);
      identifier.name("my identifier");

      string = +(char_ - qi::space)[_val += _1];
      string.name("my string");

      quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];
      quoted_string.name("my quoted string");

      variant_of_value %= ( double_with_dot
                            |
                            int_
                            |
                            (quoted_string | string) );
      variant_of_value.name("my value");

      assignment %= (identifier >> '=' >> variant_of_value);
      assignment.name("my assignment");

      header = (*skip) >> +( assignment[_val << _1] >> (*skip));
      header.name("SEP header");

      using boost::phoenix::construct;
      using boost::phoenix::val;
      qi::on_error<qi::fail>
        (
         header,
         std::cout
         << val("Error! Expecting ")
         << qi::_4                               // what failed?
         << val(" here: \"")
         << construct<std::string>(qi::_3, qi::_2)   // iterators to error-pos, end
         << val("\"")
         << std::endl
         );
#ifdef CGAL_SEP_READER_DEBUG
      qi::debug(skip);
      qi::debug(skip_comment);
      qi::debug(identifier);
      qi::debug(header);
      qi::debug(variant_of_value);
      qi::debug(assignment);
      qi::debug(string);
      qi::debug(quoted_string);
#endif // CGAL_SEP_READER_DEBUG
    } // end constructor of sep_header_grammar

    typedef std::variant<double,
                           int,
                           std::string> value;
    typedef boost::tuple<std::string, value> entry_type;

    boost::spirit::qi::rule<Iterator>                    skip_comment;
    boost::spirit::qi::rule<Iterator>                    skip;
    boost::spirit::qi::rule<Iterator, std::string()>     identifier;
    boost::spirit::qi::rule<Iterator, std::string()>     quoted_string;
    boost::spirit::qi::rule<Iterator, std::string()>     string;
    boost::spirit::qi::rule<Iterator, value()>           variant_of_value;
    boost::spirit::qi::rule<Iterator, entry_type() >     assignment;
    boost::spirit::qi::rule<Iterator, SEP_header_aux() > header;
  }; // end class template sep_header_grammar<Iterator>

  bool parseHeader(std::ifstream& input, SEP_header_aux& header) {
    std::string file_content;

    input.seekg(0, std::ios::end);
    file_content.reserve(input.tellg());
    input.seekg(0, std::ios::beg);

    file_content.assign(std::istreambuf_iterator<char>(input),
                        std::istreambuf_iterator<char>());
    if(!input) return false;

    typedef std::string::const_iterator iterator;

    iterator begin(file_content.begin()), end(file_content.end());

    sep_header_grammar<iterator> grammar;

    bool b = boost::spirit::qi::parse(begin, end, grammar, header);
    return b && (begin == end);
  }
}; // end class SEP_header

} // end namespace CGAL

#endif // CGAL_SEP_HEADER_HPP
