/*
    Header for our common utilities for XML-based formats.

    Copyright (C) 2004, 2005, 2006, 2007 Robert Lipe, robertlipe@usa.net

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

 */

#ifndef XMLGENERIC_H_INCLUDED_
#define XMLGENERIC_H_INCLUDED_

#include <cassert>               // for assert

#include <QList>                 // for QList
#include <QRegularExpression>    // for QRegularExpression
#include <QString>               // for QString
#include <QXmlStreamAttributes>  // for QXmlStreamAttributes


// Maybe the XmlGeneric string callback really shouldn't have a type
// of its own; this was a crutch during the move from char* to QString.
// It's "just" a search and replace to make it go away, but it might
// be convenient to overload some day.
using xg_string = const QString&;

enum xg_cb_type {
  cb_start = 1,
  cb_cdata,
  cb_end,
};

class XgCallbackBase
{
public:
  XgCallbackBase() = default;
  virtual ~XgCallbackBase() = default;
  XgCallbackBase(const XgCallbackBase&) = delete;
  XgCallbackBase& operator=(const XgCallbackBase&) = delete;
  XgCallbackBase(XgCallbackBase&&) = delete;
  XgCallbackBase& operator=(XgCallbackBase&&) = delete;

  virtual void operator()(xg_string string, const QXmlStreamAttributes* attrs) const = 0;
};

template<class XgFormat>
class XgFunctor : public XgCallbackBase
{
public:
  using XgCb = void (XgFormat::*)(xg_string, const QXmlStreamAttributes*);
  XgFunctor(XgFormat* obj, XgCb cb) : that_(obj), cb_(cb) {}
  void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override
  {
    (that_->*cb_)(string, attrs);
  }

private:
  XgFormat* that_;
  XgCb cb_;
};

class XgFunctionPtrCallback : public XgCallbackBase
{
public:
  using XgCb = void (xg_string, const QXmlStreamAttributes*);
  explicit XgFunctionPtrCallback(XgCb cb) : cb_(cb) {}
  void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override
  {
    (*cb_)(string, attrs);
  }

private:
  XgCb* cb_;
};

// xml processing uses a QList<xg_tag_map_entry>.
// You may generated this yourself.  See method 1 below.
// Or it may be generated for you using one of the subsequent
// methods.
struct xg_tag_map_entry {
  XgCallbackBase* tag_cb;
  xg_cb_type cb_type;
  QRegularExpression tag_re;
};

// Table generation from an array containing function pointers.
// The above table can be generated by xml_init.  See method 2 below.
// This is how things done historically before the Format class was
// introduced.
using xg_callback = void (xg_string, const QXmlStreamAttributes*);
struct xg_tag_mapping {
  xg_callback* tag_cb;
  xg_cb_type cb_type;
  const char* tag_pattern;
};

// Table generation from a list containing member function pointers.
// The above table can be generated by xml_init.  See method 3 below.
template<class MyFormat>
struct xg_functor_map_entry {
  using XgCb = void (MyFormat::*)(xg_string, const QXmlStreamAttributes*);
  XgCb tag_cb;
  xg_cb_type cb_type;
  const char* tag_pattern;
};

template<class MyFormat, typename my_functor_map_entry>
QList<xg_tag_map_entry>* build_xg_tag_map(MyFormat* instance, const QList<my_functor_map_entry>& map)
{
  auto* tag_tbl = new QList<xg_tag_map_entry>;
  for (const auto& entry : map) {
    auto* tag_cb = new XgFunctor<MyFormat>(instance, entry.tag_cb);
    QRegularExpression re(QRegularExpression::anchoredPattern(entry.tag_pattern));
    assert(re.isValid());
    tag_tbl->append({tag_cb, entry.cb_type, re});
  }
  return tag_tbl;
}

/*
 * There are multiple ways to initialize with xml_init.
 *
 * 1. Build your own QList<xg_tag_map_entry>, and pass it.
 *    You own the table, you must do any required clean up.
 *    Your callbacks may be a mix of function pointers wrapped in XgFunctors
 *    and non-static member functions wrapped in XgFunctionPtrCallbacks.
 *    and XgFunctionPtrCallback(for static member functions or global functions) entries.
 *    xml_init(fname, tbl, encoding, ignorelist, skiplist, false);
 *    You must set the dynamic_tbl parameter to false so xml_deninit doesn't
 *    attempt to free the table resources when xml_deinit is called.
 *
 * 2. Have xml_init build and own a table of XgFunctionPtrCallback entries
 *    from an list of function pointers, i.e. a QList of xg_tag_mapping elements.
 *    This only works when all callbacks are function pointers.
 *    xml_init(fname, tbl, encoding, ignorelist, skiplist);
 *    Generated table entries will automatically be freed.
 *
 * 3. Have xml_init build and own a table of XgFunctor entries from a list
 *    of non-static member functions, i.e. a QList<my_functor_map_entry>.
 *    This only works when all callbacks are non-static member functions.
 *    xml_init(fname, build_xg_tag_map(instance, map), encoding, ignorelist, skiplist, true);
 *    You must set the dynamic_tbl parameter to true to free the generated table
 *    resources when xml_deinit is called.
 *
 */
void xml_init(const QString& fname, const QList<xg_tag_map_entry>* tbl, const char* encoding,
              const char* const* ignorelist = nullptr,
              const char* const* skiplist = nullptr, bool dynamic_tbl = false);
void xml_init(const QString& fname, const QList<xg_tag_mapping>& tbl,const char* encoding,
              const char* const* ignorelist = nullptr,
              const char* const* skiplist = nullptr);
void xml_read();
void xml_readstring(const char* str);
void xml_readprefixstring(const char* str);
void xml_readunicode(const QString& str);
void xml_deinit();

#endif  // XMLGENERIC_H_INCLUDED_
