// ---------------------------------------------------------------------------
// - Automaton.cpp                                                           -
// - afnix:gfx module - automaton class implementation                       -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Vector.hpp"
#include "Gfxsid.hxx"
#include "Integer.hpp"
#include "Automaton.hpp"
#include "Evaluable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "array.tcc"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // the state/transition pair
  struct s_stap {
    // the state object
    State* p_stte;
    // the transition object
    Transition* p_trnt;
    // create a nil stap
    s_stap (void) {
      p_stte = nullptr;
      p_trnt = nullptr;
    }
    // create a stap by state
    s_stap (State* stte, Transition* trnt) {
      Object::iref (p_stte = stte);
      Object::iref (p_trnt = trnt);
    }
    // copy construct this stap
    s_stap (const s_stap& that) {
      Object::iref (p_stte = that.p_stte);
      Object::iref (p_trnt = that.p_trnt);
    }
    // destroy this stap
    ~s_stap (void) {
      Object::dref (p_stte);
      Object::dref (p_trnt);
    }
    // assign a stap to this one
    s_stap& operator = (const s_stap& that) {
      if (this == &that) return *this;
      Object::iref (that.p_stte); Object::dref (p_stte); p_stte = that.p_stte;
      Object::iref (that.p_trnt); Object::dref (p_trnt); p_trnt = that.p_trnt;
      return *this;
    }
  };
  
  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a nil automaton

  Automaton::Automaton (void) {
    p_glob = nullptr;
    p_stap = new t_array<s_stap>;
  }

  // create an automaton by global

  Automaton::Automaton (Global* glob) {
    p_glob = nullptr;
    p_stap = new t_array<s_stap>;
    if (bind (glob) == false) {
      throw Exception ("automaton-error", "cannot create automaton by global");
    }
  }
  
  // copy construct this automaton

  Automaton::Automaton (const Automaton& that) {
    that.rdlock ();
    try {
      Object::iref (p_glob = that.p_glob);
      p_stap = new t_array<s_stap>(*that.p_stap);
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
  
  // destroy this automaton

  Automaton::~Automaton (void) {
    Object::dref (p_glob);
    delete p_stap;
  }

  // assign an automaton to this one

  Automaton& Automaton::operator = (const Automaton& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      // delete old array
      delete p_stap; p_stap = nullptr;
      // assign locally
      Object::iref (that.p_glob); Object::dref (p_glob); p_glob = that.p_glob;
      p_stap = new t_array<s_stap>(*that.p_stap);
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }
  
  // return the automaton class name

  String Automaton::repr (void) const {
    return "Automaton";
  }

  // return a clone of this object
  
  Object* Automaton::clone (void) const {
    return new Automaton (*this);
  }
  
  // return the serial did

  t_word Automaton::getdid (void) const {
    return SRL_DEOD_GFX;
  }

  // return the serial sid

  t_word Automaton::getsid (void) const {
    return SRL_ATMT_SID;
  }
  
  // serialize this automaton

  void Automaton::wrstream (OutputStream& os) const {
    rdlock ();
    try {
      // serialize global
      if (p_glob == nullptr) {
	Serial::wrnilid (os);
      } else {
	p_glob->serialize (os);
      }
      // serialize snaps
      long alen = p_stap->length ();
      Serial::wrlong (alen, os);
      for (long k = 0L; k < alen; k++) {
	auto stap = (*p_stap)[k];
	if (stap.p_stte == nullptr) {
	  Serial::wrnilid (os);
	} else {
	  stap.p_stte->serialize (os);
	}
	if (stap.p_trnt == nullptr) {
	  Serial::wrnilid (os);
	} else {
	  stap.p_trnt->serialize (os);
	}
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // deserialize this automaton

  void Automaton::rdstream (InputStream& is) {
    wrlock ();
    try {
      // reset object
      reset ();
      // desrialize global
      Object::iref (p_glob = dynamic_cast<Global*>(Serial::deserialize (is)));
      // deserialize snaps
      long alen = Serial::rdlong (is);
      for (long k = 0L; k < alen; k++) {
	auto stte = dynamic_cast<State*>(Serial::deserialize (is));
	auto trnt = dynamic_cast<Transition*>(Serial::deserialize (is));
	add (stte, trnt);
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // reset this automaton

  void Automaton::reset (void) {
    wrlock ();
    try {
      Object::dref (p_glob); p_glob = nullptr;
      delete p_stap; p_stap = new t_array<s_stap>;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // bind a global to this automaton

  bool Automaton::bind (Global* glob) {
    wrlock ();
    try {
      Object::iref (glob); Object::dref (p_glob); p_glob = glob;
      unlock ();
      return true;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // get the automaton length

  long Automaton::length (void) const {
    rdlock ();
    try {
      long result = p_stap->length ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a state to the automaton

  void Automaton::add (State* stte) {
    wrlock ();
    try {
      if (stte == nullptr) {
	unlock ();
	return;
      }
      add (stte, new Transition);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // add a state to the automaton

  void Automaton::add (State* stte, Transition* trnt) {
    wrlock ();
    try {
      // check for nil
      if ((stte == nullptr) && (trnt != nullptr)) {
	throw Exception ("automaton-error", "invalid state/transition");
      }
      if (trnt == nullptr) trnt = new Transition;
      // set the state index
      stte->setsidx (p_stap->length ());
      // push a new stap by state
      p_stap->push (s_stap (stte, trnt));
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a state by stap index

  State* Automaton::getstte (const long sidx) const {
    rdlock ();
    try {
      State* result = (*p_stap)[sidx].p_stte;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a transition by stap index

  Transition* Automaton::gettrnt (const long sidx) const {
    rdlock ();
    try {
      Transition* result = (*p_stap)[sidx].p_trnt;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // return a new automaton iterator

  Iterator* Automaton::makeit (void) {
    rdlock ();
    try {
      Iterator* result = new Automatic (this);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get an iterator at the beginning

  Automatic Automaton::begin (void) {
    rdlock ();
    try {
      Automatic result = this;
      result.begin ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // get an iterator at the end

  Automatic Automaton::end (void) {
    rdlock ();
    try {
      Automatic result = this;
      result.end ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 5;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the automaton supported quarks
  static const long QUARK_ADD     = zone.intern ("add");
  static const long QUARK_RESET   = zone.intern ("reset");
  static const long QUARK_LENGTH  = zone.intern ("length");
  static const long QUARK_GETSTTE = zone.intern ("get-state");
  static const long QUARK_GETTRNT = zone.intern ("get-transition");

  // create a new object in a generic way

  Object* Automaton::mknew (Vector* argv) {
    long argc = (argv == nullptr) ? 0 : argv->length ();

    // check for 0 argument
    if (argc == 0) return new Automaton;
    // invalid arguments
    throw Exception ("argument-error", "too many arguments with automaton");
  }

  // return true if the given quark is defined

  bool Automaton::isquark (const long quark, const bool hflg) const {
    rdlock ();
    try {
      if (zone.exists (quark) == true) {
	unlock ();
	return true;
      }
      bool result = hflg ? Iterable::isquark (quark, hflg) : false;
      if (result == false) {
	result = hflg ? Serial::isquark (quark, hflg) : false;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // apply this object with a set of arguments and a quark
  
  Object* Automaton::apply (Evaluable* zobj, Nameset* nset, const long quark,
			    Vector* argv) {
    // get the number of arguments
    long argc = (argv == nullptr) ? 0 : argv->length ();
    
    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_LENGTH) return new Integer (length ());
      if (quark == QUARK_RESET) {
	reset ();
	return nullptr;
      }
    }

    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_ADD) {
	// collect object
	Object* obj = argv->get (0);
	// check for a state
	auto stte = dynamic_cast <State*> (obj);
	if (stte != nullptr) {
	  add (stte);
	  return stte;
	}
	// invalid object
	throw Exception ("type-error", "invalid object to add to automaton",
			 Object::repr (obj));
      }
      if (quark == QUARK_GETSTTE) {
	rdlock ();
	try {
	  long sidx = argv->getlong (0);
	  Object* result = getstte (sidx);
	  zobj->post (result);
	  unlock ();
	  return result;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
      if (quark == QUARK_GETTRNT) {
	rdlock ();
	try {
	  long sidx = argv->getlong (0);
	  Object* result = gettrnt (sidx);
	  zobj->post (result);
	  unlock ();
	  return result;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
    }
    // check the iterable method
    if (Iterable::isquark (quark, true) == true) {
      return Iterable::apply (zobj, nset, quark, argv);
    }
    // call the serial method
    return Serial::apply (zobj, nset, quark, argv);
  }

  // -------------------------------------------------------------------------
  // - iterator section                                                      -
  // -------------------------------------------------------------------------

  // create a new automaton iterator

  Automatic::Automatic (Automaton* aobj) {
    Object::iref (p_aobj = aobj);
    begin ();
  }

  // copy construct this iterator

  Automatic::Automatic (const Automatic& that) {
    that.rdlock ();
    try {
      Object::iref (p_aobj = that.p_aobj);
      d_sidx = that.d_sidx;
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
  
  // destroy this automaton iterator

  Automatic::~Automatic (void) {
    Object::dref (p_aobj);
  }

  // assign an iterator to this one

  Automatic& Automatic::operator = (const Automatic& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      Object::iref (that.p_aobj);
      Object::dref (p_aobj);
      p_aobj = that.p_aobj;
      d_sidx = that.d_sidx;
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }

  // compare two iterators

  bool Automatic::operator == (const Automatic& it) const {
    rdlock ();
    try {
      bool result = (p_aobj == it.p_aobj) && (d_sidx == it.d_sidx);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // compare two iterators

  bool Automatic::operator != (const Automatic& it) const {
    rdlock ();
    try {
      bool result = (p_aobj != it.p_aobj) || (d_sidx != it.d_sidx);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // move the iterator to the next position

  Automatic& Automatic::operator ++ (void) {
    wrlock ();
    try {
      next ();
      unlock ();
      return *this;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // move the iterator to the previous position

  Automatic& Automatic::operator -- (void) {
    wrlock ();
    try {
      prev ();
      unlock ();
      return *this;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // get the iterator object

  Object* Automatic::operator * (void) const {
    rdlock ();
    try {
      Object* result = getobj ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
	
  // return the class name

  String Automatic::repr (void) const {
    return "Automatic";
  }

  // reset the iterator to the begining

  void Automatic::begin (void) {
    wrlock ();
    try {
      d_sidx = 0L;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // reset the iterator to the end

  void Automatic::end (void) {
    wrlock ();
    try {
      d_sidx = (p_aobj == nullptr) ? 0L : p_aobj->length ();
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // go to the next object

  void Automatic::next (void) {
    wrlock ();
    try {
      if (p_aobj == nullptr) {
	unlock ();
	return;
      }
      // check for end
      long alen = p_aobj->length ();
      if (d_sidx == alen) {
	unlock ();
	return;
      }
      // collect state and transition
      State* stte = p_aobj->getstte (d_sidx);
      Transition* trnt = p_aobj->gettrnt (d_sidx);
      if (trnt == nullptr) {
	throw Exception ("iterator-error", "null transition in automaton");
      }
      // compute next state index
      d_sidx = trnt->nxsidx (p_aobj->p_glob, stte);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // go to the previous object

  void Automatic::prev (void) {
    throw Exception ("iterator-error",
		     "cannot move back an automaton iterator");
  }

  // get the object at the current position

  Object* Automatic::getobj (void) const {
    rdlock ();
    try {
      if (p_aobj == nullptr) {
	unlock ();
	return nullptr;
      }
      if (d_sidx >= p_aobj->length ()) {
	unlock ();
	return nullptr;
      }
      Object* result = p_aobj->getstte (d_sidx);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return true if the iterator is at the end

  bool Automatic::isend (void) const {
    rdlock ();
    if (p_aobj != nullptr) p_aobj->rdlock ();
    try {
      if (p_aobj == nullptr) {
	unlock ();
	return true;
      }
      if (d_sidx >= p_aobj->length ()) {
	unlock ();
	return true;
      }
      unlock ();
      return false;
    } catch (...) {
      if (p_aobj != nullptr) p_aobj->unlock ();
      unlock ();
      throw;
    }
  }

  // get the client object at the current position

  Object* Automatic::getcobj (void) const {
    rdlock ();
    try {
      if (p_aobj == nullptr) {
	unlock ();
	return nullptr;
      }
      if (d_sidx >= p_aobj->length ()) {
	unlock ();
	return nullptr;
      }
      // collect iterator state
      State* stte = p_aobj->getstte (d_sidx);
      // collect client object
      Object* result = (stte == nullptr) ? nullptr : stte->getcobj ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
}
