
// 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: jpr@google.com (Jake Ratkiewicz)

// Represents a generic weight in an FST -- that is, represents a specific
// type of weight underneath while hiding that type from a client.


#ifndef FST_SCRIPT_WEIGHT_CLASS_H_
#define FST_SCRIPT_WEIGHT_CLASS_H_

#include <string>

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

namespace fst {
namespace script {

class WeightImplBase {
 public:
  virtual WeightImplBase *Copy() const = 0;
  virtual void Print(ostream *o) const = 0;
  virtual const string &Type() const = 0;
  virtual string to_string() const = 0;
  virtual bool operator == (const WeightImplBase &other) const = 0;
  virtual ~WeightImplBase() { }
};

template<class W>
struct WeightClassImpl : public WeightImplBase {
  W weight;

  explicit WeightClassImpl(const W& weight) : weight(weight) { }

  virtual WeightClassImpl<W> *Copy() const {
    return new WeightClassImpl<W>(weight);
  }

  virtual const string &Type() const { return W::Type(); }

  virtual void Print(ostream *o) const {
    *o << weight;
  }

  virtual string to_string() const {
    ostringstream s;
    s << weight;
    return s.str();
  }

  virtual bool operator == (const WeightImplBase &other) const {
    if (Type() != other.Type()) {
      return false;
    } else {
      const WeightClassImpl<W> *typed_other =
          static_cast<const WeightClassImpl<W> *>(&other);

      return typed_other->weight == weight;
    }
  }
};


class WeightClass {
 public:
  WeightClass() : element_type_(ZERO), impl_(0) { }

  template<class W>
  explicit WeightClass(const W& weight)
  : element_type_(OTHER), impl_(new WeightClassImpl<W>(weight)) { }

  WeightClass(const string &weight_type, const string &weight_str);

  WeightClass(const WeightClass &other) :
      element_type_(other.element_type_),
      impl_(other.impl_ ? other.impl_->Copy() : 0) { }

  WeightClass &operator = (const WeightClass &other) {
    if (impl_) delete impl_;
    impl_ = other.impl_ ? other.impl_->Copy() : 0;
    element_type_ = other.element_type_;
    return *this;
  }

  template<class W>
  const W* GetWeight() const;

  string to_string() const {
    switch (element_type_) {
      case ZERO:
        return "ZERO";
      case ONE:
        return "ONE";
      default:
      case OTHER:
        return impl_->to_string();
    }
  }

  bool operator == (const WeightClass &other) const {
    return element_type_ == other.element_type_ &&
        ((impl_ && other.impl_ && (*impl_ == *other.impl_)) ||
         (impl_ == 0 && other.impl_ == 0));
  }

  static const WeightClass &Zero() {
    static WeightClass w(ZERO);

    return w;
  }

  static const WeightClass &One() {
    static WeightClass w(ONE);

    return w;
  }

  ~WeightClass() { if (impl_) delete impl_; }
 private:
  enum ElementType { ZERO, ONE, OTHER };
  ElementType element_type_;

  WeightImplBase *impl_;

  explicit WeightClass(ElementType et) : element_type_(et), impl_(0) { }

  friend ostream &operator << (ostream &o, const WeightClass &c);
};

template<class W>
const W* WeightClass::GetWeight() const {
  // We need to store zero and one as statics, because the weight type
  // W might return them as temporaries. We're returning a pointer,
  // and it won't do to get the address of a temporary.
  static const W zero = W::Zero();
  static const W one = W::One();

  if (element_type_ == ZERO) {
    return &zero;
  } else if (element_type_ == ONE) {
    return &one;
  } else {
    if (W::Type() != impl_->Type()) {
      return NULL;
    } else {
      WeightClassImpl<W> *typed_impl =
          static_cast<WeightClassImpl<W> *>(impl_);
      return &typed_impl->weight;
    }
  }
}

//
// Registration for generic weight types.
//

typedef WeightImplBase* (*StrToWeightImplBaseT)(const string &str,
                                                const string &src,
                                                size_t nline);

template<class W>
WeightImplBase* StrToWeightImplBase(const string &str,
                                    const string &src, size_t nline) {
  return new WeightClassImpl<W>(StrToWeight<W>(str, src, nline));
}

// The following confuses swig, and doesn't need to be wrapped anyway.
#ifndef SWIG
ostream& operator << (ostream &o, const WeightClass &c);

class WeightClassRegister : public GenericRegister<string,
                                                   StrToWeightImplBaseT,
                                                   WeightClassRegister> {
 protected:
  virtual string ConvertKeyToSoFilename(const string &key) const {
    return key + ".so";
  }
};

typedef GenericRegisterer<WeightClassRegister> WeightClassRegisterer;
#endif

// internal version, needs to be called by wrapper in order for
// macro args to expand
#define REGISTER_FST_WEIGHT__(Weight, line)                             \
  static WeightClassRegisterer weight_registerer ## _ ## line(          \
      Weight::Type(),                                                   \
      StrToWeightImplBase<Weight>)

// This layer is where __FILE__ and __LINE__ are expanded
#define REGISTER_FST_WEIGHT_EXPANDER(Weight, line)      \
  REGISTER_FST_WEIGHT__(Weight, line)

//
// Macro for registering new weight types. Clients call this.
//
#define REGISTER_FST_WEIGHT(Weight) \
  REGISTER_FST_WEIGHT_EXPANDER(Weight, __LINE__)

}  // namespace script
}  // namespace fst

#endif  // FST_SCRIPT_WEIGHT_CLASS_H_
