// Copyright 2005-2024 Google LLC
//
// 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.
//
// See www.openfst.org for extensive documentation on this weighted
// finite-state transducer library.
//
// Regression test for FST classes.

#ifndef FST_TEST_FST_TEST_H_
#define FST_TEST_FST_TEST_H_

#include <cstddef>
#include <memory>
#include <string>

#include <fst/log.h>
#include <fst/equal.h>
#include <fst/expanded-fst.h>
#include <fstream>
#include <fst/fst.h>
#include <fst/matcher.h>
#include <fst/mutable-fst.h>
#include <fst/properties.h>
#include <fst/vector-fst.h>
#include <fst/verify.h>

namespace fst {

// This tests an Fst F that is assumed to have a copy method from an
// arbitrary Fst. Some test functions make further assumptions mostly
// obvious from their name. These tests are written as member temple
// functions that take a test fst as its argument so that different
// Fsts in the interface hierarchy can be tested separately and so
// that we can instantiate only those tests that make sense for a
// particular Fst.
template <class F>
class FstTester {
 public:
  using Arc = typename F::Arc;
  using StateId = typename Arc::StateId;
  using Weight = typename Arc::Weight;
  using Label = typename Arc::Label;

  explicit FstTester(size_t num_states = 128, bool weighted = true)
      : num_states_(num_states), weighted_(weighted) {
    VectorFst<Arc> vfst;
    InitFst(&vfst, num_states);
    testfst_ = std::make_unique<F>(vfst);
  }

  // This verifies the contents described in InitFst() using
  // methods defined in a generic Fst.
  template <class G>
  void TestBase(const G &fst) const {
    StateId ns = 0;
    StateIterator<G> siter(fst);
    Matcher<G> matcher(fst, MATCH_INPUT);
    MatchType match_type = matcher.Type(true);
    bool has_states = false;
    for (; !siter.Done(); siter.Next()) {
      has_states = true;
    }
    CHECK_EQ(fst.Start(), has_states ? 0 : kNoStateId);
    for (siter.Reset(); !siter.Done(); siter.Next()) {
      StateId s = siter.Value();
      matcher.SetState(s);
      CHECK_EQ(fst.Final(s), NthWeight(s));
      size_t na = 0;
      ArcIterator<G> aiter(fst, s);
      for (; !aiter.Done(); aiter.Next()) {
      }
      for (aiter.Reset(); !aiter.Done(); aiter.Next()) {
        ++na;
        const Arc &arc = aiter.Value();
        CHECK_EQ(arc.ilabel, na);
        CHECK_EQ(arc.olabel, 0);
        CHECK_EQ(arc.weight, NthWeight(na));
        if (na == ns + 1) {
          CHECK_EQ(arc.nextstate, s == num_states_ - 1 ? 0 : s + 1);
        } else {
          CHECK_EQ(arc.nextstate, s);
        }
        if (match_type == MATCH_INPUT) {
          CHECK(matcher.Find(arc.ilabel));
          CHECK_EQ(matcher.Value().ilabel, arc.ilabel);
        }
      }
      CHECK_EQ(na, s + 1);
      CHECK_EQ(na, aiter.Position());
      CHECK_EQ(fst.NumArcs(s), s + 1);
      CHECK_EQ(fst.NumInputEpsilons(s), 0);
      CHECK_EQ(fst.NumOutputEpsilons(s), s + 1);
      CHECK(!matcher.Find(s + 2));     // out-of-range
      CHECK(!matcher.Find(kNoLabel));  // no explicit input epsilons
      CHECK(matcher.Find(0));
      CHECK_EQ(matcher.Value().ilabel, kNoLabel);  // implicit epsilon loop
      ++ns;
    }
    CHECK_EQ(num_states_, ns);
    CHECK(Verify(fst));
    CHECK(fst.Properties(ns > 0 ? kNotAcceptor : kAcceptor, true));
    CHECK(fst.Properties(ns > 0 ? kOEpsilons : kNoOEpsilons, true));
  }

  void TestBase() const { TestBase(*testfst_); }

  // This verifies methods specfic to an ExpandedFst.
  template <class G>
  void TestExpanded(const G &fst) const {
    CHECK_EQ(fst.NumStates(), num_states_);
    StateId ns = 0;
    for (StateIterator<G> siter(fst); !siter.Done(); siter.Next()) {
      ++ns;
    }
    CHECK_EQ(fst.NumStates(), ns);
    CHECK(fst.Properties(kExpanded, false));
  }

  void TestExpanded() const { TestExpanded(*testfst_); }

  // This verifies methods specific to a MutableFst.
  template <class G>
  void TestMutable(G *fst) const {
    for (StateIterator<G> siter(*fst); !siter.Done(); siter.Next()) {
      StateId s = siter.Value();
      size_t na = 0;
      size_t ni = fst->NumInputEpsilons(s);
      MutableArcIterator<G> aiter(fst, s);
      for (; !aiter.Done(); aiter.Next()) {
      }
      for (aiter.Reset(); !aiter.Done(); aiter.Next()) {
        ++na;
        Arc arc = aiter.Value();
        arc.ilabel = 0;
        aiter.SetValue(arc);
        arc = aiter.Value();
        CHECK_EQ(arc.ilabel, 0);
        CHECK_EQ(fst->NumInputEpsilons(s), ni + 1);
        arc.ilabel = na;
        aiter.SetValue(arc);
        CHECK_EQ(fst->NumInputEpsilons(s), ni);
      }
    }

    {
      std::unique_ptr<G> cfst1(fst->Copy());
      cfst1->DeleteStates();
      CHECK_EQ(cfst1->NumStates(), 0);
    }

    std::unique_ptr<G> cfst2(fst->Copy());
    for (StateIterator<G> siter(*cfst2); !siter.Done(); siter.Next()) {
      StateId s = siter.Value();
      cfst2->DeleteArcs(s);
      CHECK_EQ(cfst2->NumArcs(s), 0);
      CHECK_EQ(cfst2->NumInputEpsilons(s), 0);
      CHECK_EQ(cfst2->NumOutputEpsilons(s), 0);
    }
  }

  void TestMutable() { TestMutable(testfst_.get()); }

  // This verifies operator=
  template <class G>
  void TestAssign(const G &fst) const {
    // Assignment from G
    G afst1;
    afst1 = fst;
    CHECK(Equal(fst, afst1));

    // Assignment from Fst
    G afst2;
    afst2 = static_cast<const Fst<Arc> &>(fst);
    CHECK(Equal(fst, afst2));

    // Assignment from self
    afst2.operator=(afst2);
    CHECK(Equal(fst, afst2));
  }

  void TestAssign() { TestAssign(*testfst_); }

  // This verifies the copy constructor and Copy method.
  template <class G>
  void TestCopy(const G &fst) const {
    // Copy from G
    G c1fst(fst);
    TestBase(c1fst);

    // Copy from Fst
    const G c2fst(static_cast<const Fst<Arc> &>(fst));
    TestBase(c2fst);

    // Copy from self
    std::unique_ptr<const G> c3fst(fst.Copy());
    TestBase(*c3fst);
  }

  void TestCopy() const { TestCopy(*testfst_); }

  // This verifies the read/write methods.
  template <class G>
  void TestIO(const G &fst) const {
    const std::string filename = FST_FLAGS_tmpdir + "/test.fst";
    const std::string aligned = FST_FLAGS_tmpdir + "/aligned.fst";
    {
      // write/read
      CHECK(fst.Write(filename));
      auto ffst = fst::WrapUnique(G::Read(filename));
      CHECK(ffst);
      TestBase(*ffst);
    }

    {
      // generic read/cast/test
      auto gfst = fst::WrapUnique(Fst<Arc>::Read(filename));
      CHECK(gfst);
      G *dfst = down_cast<G *>(gfst.get());
      TestBase(*dfst);

      // generic write/read/test
      CHECK(gfst->Write(filename));
      auto hfst = fst::WrapUnique(Fst<Arc>::Read(filename));
      CHECK(hfst);
      TestBase(*hfst);
    }

    {
      // check mmaping by first writing the file with the aligned attribute set
      {
        std::ofstream ostr(aligned);
        FstWriteOptions opts;
        opts.source = aligned;
        opts.align = true;
        CHECK(fst.Write(ostr, opts));
      }
      std::ifstream istr(aligned);
      FstReadOptions opts;
      opts.mode = FstReadOptions::ReadMode("map");
      opts.source = aligned;
      auto gfst = fst::WrapUnique(G::Read(istr, opts));
      CHECK(gfst);
      TestBase(*gfst);
    }

    // check mmaping of unaligned files to make sure it does not fail.
    {
      {
        std::ofstream ostr(aligned);
        FstWriteOptions opts;
        opts.source = aligned;
        opts.align = false;
        CHECK(fst.Write(ostr, opts));
      }
      std::ifstream istr(aligned);
      FstReadOptions opts;
      opts.mode = FstReadOptions::ReadMode("map");
      opts.source = aligned;
      auto gfst = fst::WrapUnique(G::Read(istr, opts));
      CHECK(gfst);
      TestBase(*gfst);
    }

    // expanded write/read/test
    if (fst.Properties(kExpanded, false)) {
      auto efst = fst::WrapUnique(ExpandedFst<Arc>::Read(filename));
      CHECK(efst);
      TestBase(*efst);
      TestExpanded(*efst);
    }

    // mutable write/read/test
    if (fst.Properties(kMutable, false)) {
      auto mfst = fst::WrapUnique(MutableFst<Arc>::Read(filename));
      CHECK(mfst);
      TestBase(*mfst);
      TestExpanded(*mfst);
      TestMutable(mfst.get());
    }
  }

  void TestIO() const { TestIO(*testfst_); }

 private:
  // This constructs test FSTs. Given a mutable FST, will leave
  // the FST as follows:
  // (I) NumStates() = nstates
  // (II) Start() = 0
  // (III) Final(s) =  NthWeight(s)
  // (IV) For state s:
  //     (a) NumArcs(s) == s + 1
  //     (b) For ith arc (i: 1 to s) of s:
  //         (1) ilabel = i
  //         (2) olabel = 0
  //         (3) weight = NthWeight(i)
  //         (4) nextstate = s
  //     (c) s+1st arc of s:
  //         (1) ilabel = s + 1
  //         (2) olabel = 0
  //         (3) weight = NthWeight(s + 1)
  //         (4) nextstate = s + 1 if s < nstates - 1
  //                         0 if s == nstates - 1
  void InitFst(MutableFst<Arc> *fst, size_t nstates) const {
    fst->DeleteStates();

    for (StateId s = 0; s < nstates; ++s) {
      fst->AddState();
      fst->SetFinal(s, NthWeight(s));
      for (size_t i = 1; i <= s; ++i) {
        Arc arc(i, 0, NthWeight(i), s);
        fst->AddArc(s, arc);
      }
      fst->AddArc(
          s, Arc(s + 1, 0, NthWeight(s + 1), s == nstates - 1 ? 0 : s + 1));
    }

    if (nstates > 0) fst->SetStart(0);
  }

  // Generates One() + ... + One() (n times) if weighted_,
  // otherwise One().
  Weight NthWeight(int n) const {
    if (!weighted_) return Weight::One();
    Weight w = Weight::Zero();
    for (int i = 0; i < n; ++i) w = Plus(w, Weight::One());
    return w;
  }

  size_t num_states_ = 0;
  bool weighted_ = true;
  std::unique_ptr<F> testfst_;  // what we're testing
};

}  // namespace fst

#endif  // FST_TEST_FST_TEST_H_
