/*
 * Finite State Machine using XML4C (IBM XML Parser for C++ 3.0.1)
 */

#include <iostream>
#include <iterator>
#include <algorithm>
#include <set>
#include <string>

#include <util/PlatformUtils.hpp>
#include <dom/DOM.hpp>
#include <parsers/DOMParser.hpp>
#include <sax/ErrorHandler.hpp>
#include <sax/SAXParseException.hpp>

using namespace std;

inline string transcode(const DOMString& domstr) {
  return domstr.transcode();
}

/*
 * Parse中に発生したエラーを出力する
 */
class ErrorReporter : public ErrorHandler {
  bool ok_;
public:
  ErrorReporter() : ok_(true) {}
  ~ErrorReporter() {}

  void warning(const SAXParseException& toCatch) {}
  void error(const SAXParseException& toCatch) {
    ok_ = false;
    std::cerr << "Error at file \"" << transcode(toCatch.getSystemId())
              << "\", line " << toCatch.getLineNumber()
              << ", column " << toCatch.getColumnNumber()
              << "\n   Message: " << transcode(toCatch.getMessage())
              << "\n\n";
  }

  void fatalError(const SAXParseException& toCatch) {
    ok_ = false;
    std::cerr << "Fatal Error at file \"" << transcode(toCatch.getSystemId())
              << "\", line " << toCatch.getLineNumber()
              << ", column " << toCatch.getColumnNumber()
              << "\n   Message: " << transcode(toCatch.getMessage())
              << "\n\n";
  }

  void resetErrors() {}

  bool ok() const { return ok_; }
};

/*
 * elementからattribute_nameなるアトリビュート値を取り出す
 */
inline string get_attribute(DOM_Element element, const char* attribute_name) {
  return transcode(element.getAttribute(attribute_name));
}

/*
 * parentの子nodeの中からattribute_nameなるアトリビュート値がkeyに一致するものを見つける
 */
DOM_Element find_element(DOM_Node parent, const char* attribute_name, const string& key) {
  DOM_Element element;
  for (   DOM_Node node = parent.getFirstChild(); node != 0; node = node.getNextSibling() ) {
    if ( node.getNodeType() == DOM_Node::ELEMENT_NODE ) {
      element = (DOM_Element&)node; 
      if ( get_attribute(element,attribute_name) == key )
        return element;
    }
  }
  return element;
}

/*
 * ----- main -----
 */
int main(int argc, char* argv[]) {

  if ( argc != 2 ) {
    cerr << "fsm <URL for FSM>" << endl;
    return 1;
  }

  /*
   * XML Parser の生成と初期化 
   */
  try {
    XMLPlatformUtils::Initialize();
  } catch ( XMLException& ex ) {
    cerr << transcode(ex.getMessage()) << endl;
    return 1;
  }
  DOMParser parser;
  ErrorReporter error;
  parser.setDoValidation(true);  // 検証を行う
  parser.setErrorHandler(&error);

  /*
   * 引数に与えられたURLで示されたXMLをロードする
   */
  try {
    parser.parse(argv[1]);
    if ( !error.ok() ) {
      return 1;
    }
  } catch ( XMLException& ex ) {
    cerr << transcode(ex.getMessage()) << endl;
    return 1;
  }

  /*
   * ドキュメントを取得する
   */
  DOM_Document document = parser.getDocument();

  /*
   * ルートエレメントを取得する
   */
  DOM_Element root = document.getDocumentElement();

  /*
   * 全イベントを取り出す
   */
  set<string> event_set;
  DOM_NodeList events = root.getElementsByTagName(DOMString("Event"));
  for ( int i = 0; i < events.getLength(); ++i ) {
    DOM_Element node = (DOM_Element&)events.item(i);
    event_set.insert(get_attribute(node,"name"));
  }

  /*
   * 初期状態 / 終了状態 を取得する
   */
  string state_name     = get_attribute(root,"initial");
  string terminal_name  = get_attribute(root,"terminal");

  string fsm_name       = get_attribute(root,"name");
  cout << "FSM : " << fsm_name << endl;

  /*
   * 状態遷移表を駆動する
   */
  while ( state_name != terminal_name ) {

    /*
     * state_name で示される State を見つける
     */
    DOM_Element state  = find_element(root,"name",state_name);
    if ( state == 0 ) {
      cerr << "can't find state : " << state_name << endl;
      break;
    }  

    /*
     * 現在の状態とイベント一覧を出力する
     */
    cout << "state : " << state_name << endl;
    cout << "enter an event (";
    copy(event_set.begin(), event_set.end(), ostream_iterator<string>(cout,","));
    cout << "exit) ? " << flush;

    /*
     * ユーザからのイベント入力
     */
    string event_name;
    cin >> event_name;
    if ( event_name == "exit" )       
      break;
    if ( event_set.find(event_name) == event_set.end() ) {
      cerr << event_name << " is not valid." << endl;
      continue;
    }

    /*
     * event_name で示される Event を見つける
     */
    DOM_Element event = find_element(state,"name",event_name);
    if ( event == 0 ) {
      cerr << "can't find event : " << event_name << endl;
      break;
    }

   /*
    * アクションを実行する
    */
    string action_name = get_attribute(event,"action");
    if ( action_name != "none" )
      cout << "action : " << action_name << endl; 

   /*
    * 次の状態へ遷移する
    */      
    string transition_name = get_attribute(event,"transition"); 
    if ( transition_name != "none" )
      state_name = transition_name;
  }
  
  return 0;
}

