// See www.openfst.org for extensive documentation on this weighted
// finite-state transducer library.
//
// Classes for registering derived FST for generic reading.

#ifndef FST_REGISTER_H_
#define FST_REGISTER_H_

#include <string>
#include <type_traits>


#include <fst/compat.h>
#include <fst/generic-register.h>
#include <fst/util.h>


#include <fst/log.h>

namespace fst {

template <class Arc>
class Fst;

struct FstReadOptions;

// This class represents a single entry in a FstRegister
template <class Arc>
struct FstRegisterEntry {
  using Reader = Fst<Arc> *(*)(std::istream &istrm, const FstReadOptions &opts);
  using Converter = Fst<Arc> *(*)(const Fst<Arc> &fst);

  Reader reader;
  Converter converter;

  explicit FstRegisterEntry(Reader reader = nullptr,
                            Converter converter = nullptr)
      : reader(reader), converter(converter) {}
};

// This class maintains the correspondence between a string describing
// an FST type, and its reader and converter.
template <class Arc>
class FstRegister : public GenericRegister<std::string, FstRegisterEntry<Arc>,
                                           FstRegister<Arc>> {
 public:
  using Reader = typename FstRegisterEntry<Arc>::Reader;
  using Converter = typename FstRegisterEntry<Arc>::Converter;

  const Reader GetReader(const std::string &type) const {
    return this->GetEntry(type).reader;
  }

  const Converter GetConverter(const std::string &type) const {
    return this->GetEntry(type).converter;
  }

 protected:
  std::string ConvertKeyToSoFilename(const std::string &key) const override {
    std::string legal_type(key);
    ConvertToLegalCSymbol(&legal_type);
    return legal_type + "-fst.so";
  }
};

// This class registers an FST type for generic reading and creating.
// The type must have a default constructor and a copy constructor from
// Fst<Arc>.
template <class FST>
class FstRegisterer : public GenericRegisterer<FstRegister<typename FST::Arc>> {
 public:
  using Arc = typename FST::Arc;
  using Entry = typename FstRegister<Arc>::Entry;
  using Reader = typename FstRegister<Arc>::Reader;

  FstRegisterer()
      : GenericRegisterer<FstRegister<typename FST::Arc>>(FST().Type(),
                                                          BuildEntry()) {}

 private:
  static Fst<Arc> *ReadGeneric(std::istream &strm, const FstReadOptions &opts) {
    static_assert(std::is_base_of<Fst<Arc>, FST>::value,
                  "FST class does not inherit from Fst<Arc>");
    return FST::Read(strm, opts);
  }

  static Entry BuildEntry() {
    return Entry(&ReadGeneric, &FstRegisterer<FST>::Convert);
  }

  static Fst<Arc> *Convert(const Fst<Arc> &fst) { return new FST(fst); }
};

// Convenience macro to generate static FstRegisterer instance.
#define REGISTER_FST(FST, Arc) \
  static fst::FstRegisterer<FST<Arc>> FST##_##Arc##_registerer

// Converts an FST to the specified type.
template <class Arc>
Fst<Arc> *Convert(const Fst<Arc> &fst, const std::string &fst_type) {
  auto *reg = FstRegister<Arc>::GetRegister();
  const auto converter = reg->GetConverter(fst_type);
  if (!converter) {
    FSTERROR() << "Fst::Convert: Unknown FST type " << fst_type << " (arc type "
               << Arc::Type() << ")";
    return nullptr;
  }
  return converter(fst);
}

}  // namespace fst

#endif  // FST_REGISTER_H_
