// tuple-weight.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: allauzen@google (Cyril Allauzen)
//
// \file
// Tuple weight set operation definitions.

#ifndef FST_LIB_TUPLE_WEIGHT_H__
#define FST_LIB_TUPLE_WEIGHT_H__

#include <string>
#include <vector>
using std::vector;

#include <fst/weight.h>


DECLARE_string(fst_weight_parentheses);
DECLARE_string(fst_weight_separator);

namespace fst {

template<class W, unsigned int n> class TupleWeight;
template <class W, unsigned int n>
istream &operator>>(istream &strm, TupleWeight<W, n> &w);

// n-tuple weight, element of the n-th catersian power of W
template <class W, unsigned int n>
class TupleWeight {
 public:
  typedef TupleWeight<typename W::ReverseWeight, n> ReverseWeight;

  TupleWeight() {}

  TupleWeight(const TupleWeight &w) {
    for (size_t i = 0; i < n; ++i)
      values_[i] = w.values_[i];
  }

  template <class Iterator>
  TupleWeight(Iterator begin, Iterator end) {
    CHECK(end - begin == n);
    for (Iterator iter = begin; iter != end; ++iter)
      values_[iter - begin] = *iter;
  }

  TupleWeight(const W &w) {
    for (size_t i = 0; i < n; ++i)
      values_[i] = w;
  }

  static const TupleWeight<W, n> &Zero() {
    static const TupleWeight<W, n> zero(W::Zero());
    return zero;
  }

  static const TupleWeight<W, n> &One() {
    static const TupleWeight<W, n> one(W::One());
    return one;
  }

  static unsigned int Length() {
    return n;
  }

  istream &Read(istream &strm) {
    for (size_t i = 0; i < n; ++i)
      values_[i].Read(strm);
    return strm;
  }

  ostream &Write(ostream &strm) const {
    for (size_t i = 0; i < n; ++i)
      values_[i].Write(strm);
    return strm;
  }

  TupleWeight<W, n> &operator=(const TupleWeight<W, n> &w) {
    for (size_t i = 0; i < n; ++i)
      values_[i] = w.values_[i];
    return *this;
  }

  bool Member() const {
    bool member = true;
    for (size_t i = 0; i < n; ++i)
      member = member && values_[i].Member();
    return member;
  }

  size_t Hash() const {
    uint64 hash = 0;
    for (size_t i = 0; i < n; ++i)
      hash = 5 * hash + values_[i].Hash();
    return size_t(hash);
  }

  TupleWeight<W, n> Quantize(float delta = kDelta) const {
    TupleWeight<W, n> w;
    for (size_t i = 0; i < n; ++i)
      w.values_[i] = values_[i].Quantize(delta);
    return w;
  }

  ReverseWeight Reverse() const {
    TupleWeight<W, n> w;
    for (size_t i = 0; i < n; ++i)
      w.values_[i] = values_[i].Reverse();
    return w;
  }

  const W& Value(size_t i) const { return values_[i]; }

  void SetValue(size_t i, const W &w) { values_[i] = w; }

 protected:
  // Reads TupleWeight when there are no parentheses around tuple terms
  inline static istream &ReadNoParen(istream &strm,
                                     TupleWeight<W, n> &w,
                                     char separator) {
    int c;
    do {
      c = strm.get();
    } while (isspace(c));

    for (size_t i = 0; i < n - 1; ++i) {
      string s;
      if (i)
        c = strm.get();
      while (c != separator) {
        if (c == EOF) {
          strm.clear(std::ios::badbit);
          return strm;
        }
        s += c;
        c = strm.get();
      }
      // read (i+1)-th element
      istringstream sstrm(s);
      W r = W::Zero();
      sstrm >> r;
      w.SetValue(i, r);
    }

    // read n-th element
    W r = W::Zero();
    strm >> r;
    w.SetValue(n - 1, r);

    return strm;
  }

  // Reads TupleWeight when there are parentheses around tuple terms
  inline static istream &ReadWithParen(istream &strm,
                                       TupleWeight<W, n> &w,
                                       char separator,
                                       char open_paren,
                                       char close_paren) {
    int c;
    do {
      c = strm.get();
    } while (isspace(c));

    if (c != open_paren)
      LOG(FATAL) << " is fst_weight_parentheses flag set correcty? ";

    for (size_t i = 0; i < n - 1; ++i) {
      // read (i+1)-th element
      stack<int> parens;
      string s;
      c = strm.get();
      while (c != separator || !parens.empty()) {
        if (c == EOF) {
          strm.clear(std::ios::badbit);
          return strm;
        }
        s += c;
        // if parens encountered before separator, they must be matched
        if (c == open_paren) {
          parens.push(1);
        } else if (c == close_paren) {
          // Fail for mismatched parens
          if (parens.empty()) {
            strm.clear(std::ios::failbit);
            return strm;
          }
          parens.pop();
        }
        c = strm.get();
      }
      istringstream sstrm(s);
      W r = W::Zero();
      sstrm >> r;
      w.SetValue(i, r);
    }

    // read n-th element
    string s;
    c = strm.get();
    while (c != EOF) {
      s += c;
      c = strm.get();
    }
    CHECK(s.size() > 0);
    if (s[s.size() - 1] != close_paren)
      LOG(FATAL) << " is fst_weight_parentheses flag set correcty? ";
    s.erase(s.size() - 1, 1);
    istringstream sstrm(s);
    W r = W::Zero();
    sstrm >> r;
    w.SetValue(n - 1, r);

    return strm;
  }


 private:
  W values_[n];

  friend istream &operator>><W, n>(istream&, TupleWeight<W, n>&);
};

template <class W, unsigned int n>
inline bool operator==(const TupleWeight<W, n> &w1,
                       const TupleWeight<W, n> &w2) {
  bool equal = true;
  for (size_t i = 0; i < n; ++i)
    equal = equal && (w1.Value(i) == w2.Value(i));
  return equal;
}

template <class W, unsigned int n>
inline bool operator!=(const TupleWeight<W, n> &w1,
                       const TupleWeight<W, n> &w2) {
  bool not_equal = false;
  for (size_t i = 0; (i < n) && !not_equal; ++i)
    not_equal = not_equal || (w1.Value(i) != w2.Value(i));
  return not_equal;
}

template <class W, unsigned int n>
inline bool ApproxEqual(const TupleWeight<W, n> &w1,
                        const TupleWeight<W, n> &w2,
                        float delta = kDelta) {
  bool approx_equal = true;
  for (size_t i = 0; i < n; ++i)
    approx_equal = approx_equal &&
        ApproxEqual(w1.Value(i), w2.Value(i), delta);
  return approx_equal;
}

template <class W, unsigned int n>
inline ostream &operator<<(ostream &strm, const TupleWeight<W, n> &w) {
  CHECK(FLAGS_fst_weight_separator.size() == 1);
  char separator = FLAGS_fst_weight_separator[0];
  bool write_parens = false;
  if (!FLAGS_fst_weight_parentheses.empty()) {
    CHECK(FLAGS_fst_weight_parentheses.size() == 2);
    write_parens = true;
  }

  if (write_parens)
    strm << FLAGS_fst_weight_parentheses[0];
  for (size_t i  = 0; i < n; ++i) {
    if(i)
      strm << separator;
    strm << w.Value(i);
  }
  if (write_parens)
    strm << FLAGS_fst_weight_parentheses[1];

  return strm;
}

template <class W, unsigned int n>
inline istream &operator>>(istream &strm, TupleWeight<W, n> &w) {
  CHECK(FLAGS_fst_weight_separator.size() == 1);
  char separator = FLAGS_fst_weight_separator[0];

  if (!FLAGS_fst_weight_parentheses.empty()) {
    CHECK(FLAGS_fst_weight_parentheses.size() == 2);
    return TupleWeight<W, n>::ReadWithParen(
        strm, w, separator, FLAGS_fst_weight_parentheses[0],
        FLAGS_fst_weight_parentheses[1]);
  } else {
    return TupleWeight<W, n>::ReadNoParen(strm, w, separator);
  }
}



}  // namespace fst

#endif  // FST_LIB_TUPLE_WEIGHT_H__
