// See www.openfst.org for extensive documentation on this weighted
// finite-state transducer library.
//
// Scripting API support for FarReader and FarWriter.

#ifndef FST_EXTENSIONS_FAR_FAR_CLASS_H_
#define FST_EXTENSIONS_FAR_FAR_CLASS_H_

#include <memory>
#include <string>
#include <vector>

#include <fst/extensions/far/far.h>
#include <fst/script/arg-packs.h>
#include <fst/script/fstscript.h>

namespace fst {
namespace script {

// FarReader API.

// Virtual interface implemented by each concrete FarReaderImpl<A>.
// See the FarReader interface in far.h for the exact semantics.
class FarReaderImplBase {
 public:
  virtual const std::string &ArcType() const = 0;
  virtual bool Done() const = 0;
  virtual bool Error() const = 0;
  virtual const std::string &GetKey() const = 0;
  virtual const FstClass *GetFstClass() const = 0;
  virtual bool Find(const std::string &key) = 0;
  virtual void Next() = 0;
  virtual void Reset() = 0;
  virtual FarType Type() const = 0;
  virtual ~FarReaderImplBase() {}
};

// Templated implementation.
template <class Arc>
class FarReaderClassImpl : public FarReaderImplBase {
 public:
  explicit FarReaderClassImpl(const std::string &source)
      : impl_(FarReader<Arc>::Open(source)) {}

  explicit FarReaderClassImpl(const std::vector<std::string> &sources)
      : impl_(FarReader<Arc>::Open(sources)) {}

  const std::string &ArcType() const final { return Arc::Type(); }

  bool Done() const final { return impl_->Done(); }

  bool Error() const final { return impl_->Error(); }

  bool Find(const std::string &key) final { return impl_->Find(key); }

  const FstClass *GetFstClass() const final {
    fstc_ = fst::make_unique<FstClass>(*impl_->GetFst());
    return fstc_.get();
  }

  const std::string &GetKey() const final { return impl_->GetKey(); }

  void Next() final { return impl_->Next(); }

  void Reset() final { impl_->Reset(); }

  FarType Type() const final { return impl_->Type(); }

  const FarReader<Arc> *GetImpl() const { return impl_.get(); }

  FarReader<Arc> *GetImpl() { return impl_.get(); }

 private:
  std::unique_ptr<FarReader<Arc>> impl_;
  mutable std::unique_ptr<FstClass> fstc_;
};

class FarReaderClass;

using OpenFarReaderClassArgs =
    WithReturnValue<FarReaderClass *, const std::vector<std::string> &>;

// Untemplated user-facing class holding a templated pimpl.
class FarReaderClass {
 public:
  const std::string &ArcType() const { return impl_->ArcType(); }

  bool Done() const { return impl_->Done(); }

  // Returns True if the impl is null (i.e., due to read failure).
  // Attempting to call any other function will result in null dereference.
  bool Error() const { return (impl_) ? impl_->Error() : true; }

  bool Find(const std::string &key) { return impl_->Find(key); }

  const FstClass *GetFstClass() const { return impl_->GetFstClass(); }

  const std::string &GetKey() const { return impl_->GetKey(); }

  void Next() { impl_->Next(); }

  void Reset() { impl_->Reset(); }

  FarType Type() const { return impl_->Type(); }

  template <class Arc>
  const FarReader<Arc> *GetFarReader() const {
    if (Arc::Type() != ArcType()) return nullptr;
    const FarReaderClassImpl<Arc> *typed_impl =
        static_cast<FarReaderClassImpl<Arc> *>(impl_.get());
    return typed_impl->GetImpl();
  }

  template <class Arc>
  FarReader<Arc> *GetFarReader() {
    if (Arc::Type() != ArcType()) return nullptr;
    FarReaderClassImpl<Arc> *typed_impl =
        static_cast<FarReaderClassImpl<Arc> *>(impl_.get());
    return typed_impl->GetImpl();
  }

  template <class Arc>
  friend void OpenFarReaderClass(OpenFarReaderClassArgs *args);

  // Defined in the CC.

  static FarReaderClass *Open(const std::string &source);

  static FarReaderClass *Open(const std::vector<std::string> &sources);

 private:
  template <class Arc>
  explicit FarReaderClass(FarReaderClassImpl<Arc> *impl) : impl_(impl) {}

  std::unique_ptr<FarReaderImplBase> impl_;
};

// These exist solely for registration purposes; users should call the
// static method FarReaderClass::Open instead.

template <class Arc>
void OpenFarReaderClass(OpenFarReaderClassArgs *args) {
  args->retval = new FarReaderClass(new FarReaderClassImpl<Arc>(args->args));
}

// FarWriter API.

// Virtual interface implemented by each concrete FarWriterImpl<A>.
class FarWriterImplBase {
 public:
  // Unlike the lower-level library, this returns a boolean to signal failure
  // due to non-conformant arc types.
  virtual bool Add(const std::string &key, const FstClass &fst) = 0;
  virtual const std::string &ArcType() const = 0;
  virtual bool Error() const = 0;
  virtual FarType Type() const = 0;
  virtual ~FarWriterImplBase() {}
};

// Templated implementation.
template <class Arc>
class FarWriterClassImpl : public FarWriterImplBase {
 public:
  explicit FarWriterClassImpl(const std::string &source,
                              FarType type = FarType::DEFAULT)
      : impl_(FarWriter<Arc>::Create(source, type)) {}

  bool Add(const std::string &key, const FstClass &fst) final {
    if (ArcType() != fst.ArcType()) {
      FSTERROR() << "Cannot write FST with " << fst.ArcType() << " arcs to "
                 << "FAR with " << ArcType() << " arcs";
      return false;
    }
    impl_->Add(key, *(fst.GetFst<Arc>()));
    return true;
  }

  const std::string &ArcType() const final { return Arc::Type(); }

  bool Error() const final { return impl_->Error(); }

  FarType Type() const final { return impl_->Type(); }

  const FarWriter<Arc> *GetImpl() const { return impl_.get(); }

  FarWriter<Arc> *GetImpl() { return impl_.get(); }

 private:
  std::unique_ptr<FarWriter<Arc>> impl_;
};

class FarWriterClass;

using CreateFarWriterClassInnerArgs = std::pair<const std::string &, FarType>;

using CreateFarWriterClassArgs =
    WithReturnValue<FarWriterClass *, CreateFarWriterClassInnerArgs>;

// Untemplated user-facing class holding a templated pimpl.
class FarWriterClass {
 public:
  static FarWriterClass *Create(const std::string &source,
                                const std::string &arc_type,
                                FarType type = FarType::DEFAULT);

  bool Add(const std::string &key, const FstClass &fst) {
    return impl_->Add(key, fst);
  }

  // Returns True if the impl is null (i.e., due to construction failure).
  // Attempting to call any other function will result in null dereference.
  bool Error() const { return (impl_) ? impl_->Error() : true; }

  const std::string &ArcType() const { return impl_->ArcType(); }

  FarType Type() const { return impl_->Type(); }

  template <class Arc>
  const FarWriter<Arc> *GetFarWriter() const {
    if (Arc::Type() != ArcType()) return nullptr;
    const FarWriterClassImpl<Arc> *typed_impl =
        static_cast<FarWriterClassImpl<Arc> *>(impl_.get());
    return typed_impl->GetImpl();
  }

  template <class Arc>
  FarWriter<Arc> *GetFarWriter() {
    if (Arc::Type() != ArcType()) return nullptr;
    FarWriterClassImpl<Arc> *typed_impl =
        static_cast<FarWriterClassImpl<Arc> *>(impl_.get());
    return typed_impl->GetImpl();
  }

  template <class Arc>
  friend void CreateFarWriterClass(CreateFarWriterClassArgs *args);

 private:
  template <class Arc>
  explicit FarWriterClass(FarWriterClassImpl<Arc> *impl) : impl_(impl) {}

  std::unique_ptr<FarWriterImplBase> impl_;
};

// This exists solely for registration purposes; users should call the
// static method FarWriterClass::Create instead.
template <class Arc>
void CreateFarWriterClass(CreateFarWriterClassArgs *args) {
  args->retval = new FarWriterClass(new FarWriterClassImpl<Arc>(
      std::get<0>(args->args), std::get<1>(args->args)));
}

}  // namespace script
}  // namespace fst

#endif  // FST_EXTENSIONS_FAR_FAR_CLASS_H_
