// add-on.h

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright 2005-2010 Google, Inc.
// Author: riley@google.com (Michael Riley)
//
// \file
// Fst implementation class to attach an arbitrary object with a
// read/write method to an FST and its file rep. The FST is given a
// new type name.

#ifndef FST_LIB_ADD_ON_FST_H__
#define FST_LIB_ADD_ON_FST_H__

#include <stddef.h>
#include <string>

#include <fst/fst.h>


namespace fst {

// Identifies stream data as an add-on fst.
static const int32 kAddOnMagicNumber = 446681434;


//
// Some useful add-on objects.
//

// Nothing to save.
class NullAddOn {
 public:
  NullAddOn() {}

  static NullAddOn *Read(istream &istrm) {
    return new NullAddOn();
  };

  bool Write(ostream &ostrm) const { return true; }

  int RefCount() const { return ref_count_.count(); }
  int IncrRefCount() { return ref_count_.Incr(); }
  int DecrRefCount() { return ref_count_.Decr(); }

 private:
  RefCounter ref_count_;

  DISALLOW_COPY_AND_ASSIGN(NullAddOn);
};


// Create a new add-on from a pair of add-ons.
template <class A1, class A2>
class AddOnPair {
 public:
  // Argument reference count incremented.
  AddOnPair(A1 *a1, A2 *a2)
      : a1_(a1), a2_(a2) {
    if (a1_)
      a1_->IncrRefCount();
    if (a2_)
      a2_->IncrRefCount();
  }

  ~AddOnPair() {
    if (a1_ && !a1_->DecrRefCount())
      delete a1_;
    if (a2_ && !a2_->DecrRefCount())
      delete a2_;
  }

  A1 *First() const { return a1_; }
  A2 *Second() const { return a2_; }

  static AddOnPair<A1, A2> *Read(istream &istrm) {
    A1 *a1 = 0;
    bool have_addon1 = false;
    ReadType(istrm, &have_addon1);
    if (have_addon1)
      a1 = A1::Read(istrm);

    A2 *a2 = 0;
    bool have_addon2 = false;
    ReadType(istrm, &have_addon2);
    if (have_addon2)
      a2 = A2::Read(istrm);

    AddOnPair<A1, A2> *a = new AddOnPair<A1, A2>(a1, a2);
    if (a1)
      a1->DecrRefCount();
    if (a2)
      a2->DecrRefCount();
    return a;
  };

  bool Write(ostream &ostrm) const {
    bool have_addon1 = a1_;
    WriteType(ostrm, have_addon1);
    if (have_addon1)
      a1_->Write(ostrm);
    bool have_addon2 = a2_;
    WriteType(ostrm, have_addon2);
    if (have_addon2)
      a2_->Write(ostrm);
    return true;
  }

  int RefCount() const { return ref_count_.count(); }

  int IncrRefCount() {
    return ref_count_.Incr();
  }

  int DecrRefCount() {
    return ref_count_.Decr();
  }

 private:
  A1 *a1_;
  A2 *a2_;
  RefCounter ref_count_;

  DISALLOW_COPY_AND_ASSIGN(AddOnPair);
};


// Add to an Fst F a type T object. T must have a 'T* Read(istream &)',
// a 'bool Write(ostream &)' method, and 'int RecCount(), 'int IncrRefCount()'
// and 'int DecrRefCount()' methods (e.g. 'MatcherData' in matcher-fst.h).
// The result is a new Fst implemenation with type name 'type'.
template<class F, class T>
class AddOnImpl : public FstImpl<typename F::Arc> {
 public:
  typedef typename F::Arc Arc;
  typedef typename Arc::Label Label;
  typedef typename Arc::Weight Weight;
  typedef typename Arc::StateId StateId;

  using FstImpl<Arc>::SetType;
  using FstImpl<Arc>::SetProperties;
  using FstImpl<Arc>::WriteHeader;

  // If 't' is non-zero, its reference count is incremented.
  AddOnImpl(const F &fst, const string &type, T *t = 0)
      : fst_(fst), t_(t) {
    SetType(type);
    SetProperties(fst_.Properties(kFstProperties, false));
    if (t_)
      t_->IncrRefCount();
  }

  // If 't' is non-zero, its reference count is incremented.
  AddOnImpl(const Fst<Arc> &fst, const string &type, T *t = 0)
      : fst_(fst), t_(t) {
    SetType(type);
    SetProperties(fst_.Properties(kFstProperties, false));
    if (t_)
      t_->IncrRefCount();
  }

  AddOnImpl(const AddOnImpl<F, T> &impl)
      : fst_(impl.fst_), t_(impl.t_) {
    SetType(impl.Type());
    SetProperties(fst_.Properties(kCopyProperties, false));
    if (t_)
      t_->IncrRefCount();
  }

  ~AddOnImpl() {
    if (t_ && !t_->DecrRefCount())
      delete t_;
  }

  StateId Start() const { return fst_.Start(); }
  Weight Final(StateId s) const { return fst_.Final(s); }
  size_t NumArcs(StateId s) const { return fst_.NumArcs(s); }

  size_t NumInputEpsilons(StateId s) const {
    return fst_.NumInputEpsilons(s);
  }

  size_t NumOutputEpsilons(StateId s) const {
    return fst_.NumOutputEpsilons(s);
  }

  size_t NumStates() const { return fst_.NumStates(); }

  static AddOnImpl<F, T> *Read(istream &strm, const FstReadOptions &opts) {
    FstReadOptions nopts(opts);
    FstHeader hdr;
    if (!nopts.header) {
      hdr.Read(strm, nopts.source);
      nopts.header = &hdr;
    }
    AddOnImpl<F, T> *impl = new AddOnImpl<F, T>(nopts.header->FstType());
    if (!impl->ReadHeader(strm, nopts, kMinFileVersion, &hdr))
      return 0;
    delete impl;       // Used here only for checking types.

    int32 magic_number = 0;
    ReadType(strm, &magic_number);   // Ensures this is an add-on Fst.
    if (magic_number != kAddOnMagicNumber) {
      LOG(ERROR) << "AddOnImpl::Read: Bad add-on header: " << nopts.source;
      return 0;
    }

    FstReadOptions fopts(opts);
    fopts.header = 0;  // Contained header was written out.
    F *fst = F::Read(strm, fopts);
    if (!fst)
      return 0;

    T *t = 0;
    bool have_addon = false;
    ReadType(strm, &have_addon);
    if (have_addon) {   // Read add-on object if present.
      t = T::Read(strm);
      if (!t)
        return 0;
    }
    impl = new AddOnImpl<F, T>(*fst, nopts.header->FstType(), t);
    delete fst;
    if (t)
      t->DecrRefCount();
    return impl;
  }

  bool Write(ostream &strm, const FstWriteOptions &opts) const {
    FstHeader hdr;
    FstWriteOptions nopts(opts);
    nopts.write_isymbols = false;  // Let contained FST hold any symbols.
    nopts.write_osymbols = false;
    WriteHeader(strm, nopts, kFileVersion, &hdr);
    WriteType(strm, kAddOnMagicNumber);  // Ensures this is an add-on Fst.
    FstWriteOptions fopts(opts);
    fopts.write_header = true;     // Force writing contained header.
    if (!fst_.Write(strm, fopts))
      return false;
    bool have_addon = t_;
    WriteType(strm, have_addon);
    if (have_addon)                // Write add-on object if present.
      t_->Write(strm);
    return true;
  }

  void InitStateIterator(StateIteratorData<Arc> *data) const {
    fst_.InitStateIterator(data);
  }

  void InitArcIterator(StateId s, ArcIteratorData<Arc> *data) const {
    fst_.InitArcIterator(s, data);
  }

  F &GetFst() { return fst_; }

  const F &GetFst() const { return fst_; }

  T *GetAddOn() const { return t_; }

  // If 't' is non-zero, its reference count is incremented.
  void SetAddOn(T *t) {
    if (t == t_)
      return;
    if (t_ && !t_->DecrRefCount())
      delete t_;
    t_ = t;
    if (t_)
      t_->IncrRefCount();
  }

 private:
  explicit AddOnImpl(const string &type) : t_(0) {
    SetType(type);
    SetProperties(kExpanded);
  }

  // Current file format version
  static const int kFileVersion = 1;
  // Minimum file format version supported
  static const int kMinFileVersion = 1;

  F fst_;
  T *t_;

  void operator=(const AddOnImpl<F, T> &fst);  // Disallow
};

template <class F, class T> const int AddOnImpl<F, T>::kFileVersion;
template <class F, class T> const int AddOnImpl<F, T>::kMinFileVersion;


}  // namespace fst

#endif  // FST_LIB_ADD_ON_FST_H__
