// This may look like C code, but it is really -*- C++ -*-
// RTSS --- Railway Total System Simulator
// (c) TAKAGI Ryo (at Kogakuin University)
// RS_XmlReader.cc --- class RS_XmlReader functions
// -----
// ChangeLog:
// 2009. 7. 10
//  File created.
// -----

#include "RS_XmlReader.hh"
#include <cstdlib>
#include <vector>

using std :: cerr ;
using std :: endl ;
using std :: vector ;



/**********************************************************************
 * bool RS_XmlReader :: openXmlDocument ( const string & _fn_in ,
 *  const string & _drv_in , const string & _dir_in )
 * Opens an XML file and locate the first tag to be processed.
 * Returns true if successful.
 **********************************************************************/
bool
RS_XmlReader :: openXmlDocument
( string const & _fn_in ,
  string const & _drv_in ,
  string const & _dir_in )
{
  if ( has_open_document )
  {
    cerr << "Error: tried to open a XML document when another is still open"
         << endl ;
    exit ( 1 ) ;
  }
  curdoc = new TiXmlDocument ( _fn_in ) ;
  bool load_ok = curdoc -> LoadFile () ;
  if ( ! load_ok )
  {
    cerr << "Warning: failed to open file \"" << _fn_in << "\"" << endl ;
    delete curdoc ;
    return false ;
  }
  has_open_document = true ;
  current_document_file_descriptor = _fn_in ;
  current_drive = _drv_in ;
  current_directory = _dir_in ;

  // -----
  // The file must have one "root" element of tag <rtss_input_data>.
  // If multiple <rtss_input_data> tags exist, only the first one is read.
  // このXMLファイルには <rtss_input_data> というタグがなければならない。
  // <rtss_input_data> タグが複数あれば最初のだけを読む。
  // -----
  TiXmlNode const * doc_ch = curdoc -> FirstChild () ;
  while ( doc_ch && doc_ch -> Type () != TiXmlNode :: TINYXML_ELEMENT )
  {
    doc_ch = doc_ch -> NextSibling () ;
  }
  if ( ! doc_ch || doc_ch -> Type () != TiXmlNode :: TINYXML_ELEMENT
       || string ( doc_ch -> Value () ) != "rtss_input_data" )
  {
    cerr << "Failed to find element \"rtss_input_data\""
         << " in file \"" << _fn_in << "\"." << endl ;
    exit ( 1 ) ;
  }

  // -----
  // Getting the first child element of tag <rtss_input_data>.
  // <rtss_input_data> の子を取得.
  // -----
  curnode = doc_ch -> FirstChild () ;
  while ( curnode && curnode -> Type () != TiXmlNode :: TINYXML_ELEMENT )
  {
    curnode = curnode -> NextSibling () ;
  }
  if ( ! curnode || curnode -> Type () != TiXmlNode :: TINYXML_ELEMENT )
  {
    cerr << "Failed to locate a valid data tag when opening file \""
         << _fn_in << "\"." << endl ;
    exit ( 1 ) ;
  }

  // Successful. Return true.
  return true ;
}


/**********************************************************************
 * bool RS_XmlReader :: hasNextElement ()
 * Locate next unprocessed tag. Returns true if there is one and the
 * operation successful. If no unprocessed tag is found, close the
 * current document.
 **********************************************************************/
bool
RS_XmlReader :: hasNextElement
()
{
  // -----
  // Getting the first child element of tag <rtss_input_data>.
  // <rtss_input_data> の子を取得.
  // -----
  curnode = curnode -> NextSibling () ;
  while ( curnode && curnode -> Type () != TiXmlNode :: TINYXML_ELEMENT )
  {
    curnode = curnode -> NextSibling () ;
  }
  if ( ! curnode || curnode -> Type () != TiXmlNode :: TINYXML_ELEMENT )
  {
    delete curdoc ;
    has_open_document = false ;
    current_document_file_descriptor = current_drive = current_directory
      = "" ;
    return false ;
  }
  else
  {
    return true ;
  }
}



/**********************************************************************
 * bool RS_XmlReader :: processElement ()
 * Process the current element. Returns true if successful.
 **********************************************************************/
bool
RS_XmlReader :: processElement
()
{
  // -----
  // The node RS_XmlReader :: curnode must be set before calling this
  // member function. It must be of tag <create> (in the future, we may
  // want to accept tag <include> as well).
  // 変数 RS_XmlReader :: curnode には，このメンバ関数を呼ぶ前にノード
  // がセットされていなければならない。それは <create> タグのノードでな
  // ければならない（将来，<include> タグも受け入れ可能にしたくなるかも
  // 知れない）。
  // -----
  if ( ! curnode )
  {
    cerr << "Error: node not set in RS_XmlReader::processElement()."
         << endl ;
    exit ( 1 ) ;
  }
  if ( curnode -> ValueStr () == "create" )
  {
    return processCreateTag () ;
  }
  else
  {
    cerr << "Error: <" << curnode -> Value ()
         << "> tag not allowed in RS_XmlReader::processElement()." << endl ;
    exit ( 1 ) ;
  }
}



/**********************************************************************
 * bool RS_XmlReader :: processCreateTag ()
 * Process current element that is of tag <create>.
 * Returns true if successful.
 **********************************************************************/
bool
RS_XmlReader :: processCreateTag
()
{
  // ----
  // Assume curnode has the right pointer to <create> tag element (this
  // is a private function!) Process its attributes.
  // ポインタ curnode は <create> タグを正しく指していると仮定（この関数
  // は private !）。その属性を解析。
  // ----
  TiXmlAttribute const * attr = curnode -> ToElement () -> FirstAttribute () ;
  bool sw_class = false ;
  vector < string > _obj_class ;
  _obj_class . clear () ;
  vector < string > _obj_name ;
  _obj_name . clear () ;
  while ( attr )
  {
    if ( string ( attr -> Name () ) == "class" )
    {
      // Attribute "class"
      if ( sw_class )
      {
        // -----
        // Strange ... two attributes of same name?
        // 同じ属性が2つある？... エラー
        // -----
        cerr << "Error: two \"class\" attributes?" << endl ;
        exit ( 1 ) ;
      }
      _obj_class . push_back ( attr -> ValueStr () ) ;
      sw_class = true ;
    }
    else
    {
      cerr << "Error: tag <create> with incorrect attribute \""
           << attr -> Name () << "\"." << endl ;
      exit ( 1 ) ;
    }
    attr = attr -> Next () ;
  }
  if ( ! sw_class )
  {
    cerr << "Error: <create> tag not correct: missing attribute \"" ;
    cerr << "class" << "\"." << endl ;
    exit ( 1 ) ;
  }
  
  // -----
  // Locate an <under> tag from among child elements of tag <create>. If not
  // found, proceed. If there are multiple <under> tags, only the first one
  // is read.
  // <create> の子タグのうち，<under> タグを取得. なければそのまま進行。複数
  // あるばあい最初のもの以外無視する。
  // -----
  for ( TiXmlNode const * utag = curnode -> FirstChild () ; utag ;
        utag = utag -> NextSibling () )
  {
    // -----
    // Ignore non-Element node.
    // エレメント以外は無視。
    // -----
    if ( utag -> Type () != TiXmlNode :: TINYXML_ELEMENT )
      continue ;
    // -----
    // Ignore if not of tag <under>. If it is of <under> tag, then process
    // it and get down to its child node.
    // <under> タグでなければ無視。もし <under> タグであれば，それを処理し，
    // さらにその子ノードに移動する。
    // -----
    if ( utag -> ValueStr () == "under" )
    {
      _obj_class . push_back ( getClassString ( utag ) ) ;
      _obj_name . push_back ( getNameString ( utag ) ) ;
      utag = utag -> FirstChild () ;
    }
  }
  
  // -----
  // Locate a <data> tag from among child elements of tag <create>. Error if
  // not found. If there are multiple <data> tags, only the first one is read.
  // <create> の子タグのうち，<data> タグを取得. なければエラーである。
  // 複数ある場合最初のもの以外無視する。
  // -----
  bool sw_dtag = true ;
  for ( dtag = curnode -> FirstChild () ; dtag ;
        dtag = dtag -> NextSibling () )
  {
    // -----
    // Ignore non-Element node.
    // エレメント以外は無視。
    // -----
    if ( dtag -> Type () != TiXmlNode :: TINYXML_ELEMENT )
      continue ;
    // -----
    // Ignore if not of tag <data>.
    // <data> タグでなければ無視。
    // -----
    if ( dtag -> ValueStr () == "data" )
    {
      sw_dtag = false ;
      break ;
    }
  }
  if ( sw_dtag )
  {
    cerr << "Error: tag <create> without <data> subtag." << endl ;
    exit ( 1 ) ;
  }

  // ----
  // Locate the first RS_ObjectContainer using the first (or the last in the
  // queue) string in vector _obj_class. This string will have come from the
  // <create> tag itself if there is no <under> tag; otherwise it will have
  // come from the "lowest" of the tree of <under> tags.
  // _obj_class ベクトルに格納されている最初の（あるいはキューの最末尾にある）
  // 文字列をキーとして最初の RS_ObjectContainer を取り出す。この文字列は
  // <under> タグがないばあいは <create> タグから来たデータ，そうでなければ
  // <under> タグの木の「最下層」からのデータである。
  // ----
  RS_XmlReader :: iterator ij
    = this -> find ( * ( _obj_class . rbegin () ) ) ;
  if ( ij == this -> end () )
  {
    cerr << "Error: tag <create> without class initialiser \""
         << * _obj_class . rbegin () << "\"." << endl ;
    exit ( 2 ) ;
  }
  _obj_class . pop_back () ;
  RS_ObjectContainer * xoc = ij -> second ;

  // ----
  // Find the RS_ObjectContainer using the data obtained from the <under>
  // tag. This will be used to create the object.
  // <under> タグから取り出されたデータを用い，このオブジェクトの生成に用い
  // る RS_ObjectContainer を取り出す。
  // ----
  while ( _obj_name . size () ) 
  {
    RS_Object * xoj = xoc -> locate ( * ( _obj_name . rbegin () ) ) ;
    if ( ! xoj )
    {
      cerr << "Error: could not find the object with name \""
           << * _obj_name . rbegin () << "\"." << endl ;
      exit ( 2 ) ;
    }
    xoc = xoj -> getContainer ( * ( _obj_class . rbegin () ) ) ;
    if ( ! xoc )
    {
      cerr << "Error: could not find the object container of class \""
           << * _obj_class . rbegin () << "\" in object named \""
           << * _obj_name . rbegin () << "\"." << endl ;
      exit ( 2 ) ;
    }
    _obj_name . pop_back () ;
    _obj_class . pop_back () ;
  }

  // ----
  // Create the object and return the pointer.
  // ### TODO ### Add further code here so that the <using> tags will be
  // interpreted. A <using> tag will have three string type attributes (type,
  // class and name), and optionally a tree of <under> tags as its child tag.
  // The class and name attributes and the optional <under> tree will be
  // interpreted just as the class attribute in <create> tag and the optional
  // <under> tree as a direct child element of <create> are interpreted, and
  // locate a pointer to an object instance. Note that, in <using>, the name
  // of the instance will be specified in <using> tag itself, whereas in
  // <create> it will be specified in attribute of the <data> tag. Then, the
  // obtained pointer will be handed over to the acceptor function of the
  // newly created object instance, together with the type attribute of the
  // <using> tag.
  // ############
  // <under> タグから取り出されたデータを用い，このオブジェクトの生成に用い
  // る RS_ObjectContainer を取り出す。
  // ### TODO ### <using> タグを解釈するコードをここに追加。<using> タグは3
  // つの文字列型属性 (type, class および name) を必ず持つ。また，オプション
  // として <under> タグの木を子タグとして与えることができる。属性 class お
  // よび name とオプションの <under> タグの木は，<create> タグの class 属性
  // およびオプションの <under> タグの木の解釈と同様に解釈され，あるオブジェ
  // クトのインスタンスへのポインタが得られる。ここで，インスタンス名は
  // <using> タグにおいては同タグの name 属性で指定されるが，<create> タグで
  // は <data> 子タグで指定されることに注意。これにより得られたポインタは，
  // 新しくつくられたオブジェクトのインスタンスのアクセプタ関数に，<using>
  // タグの type 属性とともに渡される。
  // ----
  // -----
  // Locate a <using> tag from among child elements of tag <create>. If not
  // found, proceed. If there are multiple <using> tags, only the first one
  // is read.
  // <create> の子タグのうち，<using> タグを取得. なければそのまま進行。複数
  // あれば順次処理する。
  // -----
  vector < string > typestr ;
  vector < RS_Object * > typeobj ;
  typestr . clear () ;
  typeobj . clear () ;
  for ( TiXmlNode const * ustag = curnode -> FirstChild () ; ustag ;
        ustag = ustag -> NextSibling () )
  {
    // -----
    // Ignore non-Element node.
    // エレメント以外は無視。
    // -----
    if ( ustag -> Type () != TiXmlNode :: TINYXML_ELEMENT )
      continue ;
    // -----
    // Ignore if not of tag <using>. If it is of <using> tag, then process
    // it and get down to its child node.
    // <using> タグでなければ無視。もし <using> タグであれば，それを処理し，
    // その子ノードを（あれば）処理する。
    // -----
    if ( ustag -> ValueStr () == "using" )
    {
      _obj_class . clear () ;
      _obj_name . clear () ;
      _obj_class . push_back ( getClassString ( ustag ) ) ;
      _obj_name . push_back ( getNameString ( ustag ) ) ;
      TiXmlAttribute const * usattr
        = ustag -> ToElement () -> FirstAttribute () ;
      bool sw_type = false ;
      while ( usattr )
      {
        if ( string ( usattr -> Name () ) == "type" )
        {
          // Attribute "type"
          if ( sw_type )
          {
            // Strange ... two attributes of same name?
            // 同じ属性が2つある？... エラー
            cerr << "Error: two \"type\" attributes?" << endl ;
            exit ( 2 ) ;
          }
          typestr . push_back ( usattr -> ValueStr () ) ;
          sw_type = true ;
        }
        usattr = usattr -> Next () ;
      }
      if ( ! sw_type )
      {
        cerr << "Error: tag <" << ustag -> ValueStr ()
             << "> did not contain attribute \"type\"." << endl ;
        exit ( 1 ) ;
      }

      // -----
      // Locate an <under> tag from among child elements of tag <using>. If not
      // found, proceed. If there are multiple <under> tags, only the first one
      // is read.
      // <using> の子タグのうち，<under> タグを取得. なければそのまま進行。複数
      // あるばあい最初のもの以外無視する。
      // -----
      for ( TiXmlNode const * utag = ustag -> FirstChild () ; utag ;
            utag = utag -> NextSibling () )
      {
        // -----
        // Ignore non-Element node.
        // エレメント以外は無視。
        // -----
        if ( utag -> Type () != TiXmlNode :: TINYXML_ELEMENT )
          continue ;
        // -----
        // Ignore if not of tag <under>. If it is of <under> tag, then process
        // it and get down to its child node.
        // <under> タグでなければ無視。もし <under> タグであれば，それを処理し，
        // さらにその子ノードに移動する。
        // -----
        if ( utag -> ValueStr () == "under" )
        {
          _obj_class . push_back ( getClassString ( utag ) ) ;
          _obj_name . push_back ( getNameString ( utag ) ) ;
          utag = utag -> FirstChild () ;
        }
      }

      // ----
      // Locate the first RS_ObjectContainer using the first (or the last in
      // the queue) string in vector _obj_class. This string will have come
      // from the <using> tag itself if there is no <under> tag; otherwise it
      // will have come from the "lowest" of the tree of <under> tags.
      // _obj_class ベクトルに格納されている最初の（あるいはキューの最末尾に
      // ある）文字列をキーとして最初の RS_ObjectContainer を取り出す。
      // この文字列は <under> タグがないばあいは <using> タグから来たデータ，
      // そうでなければ <under> タグの木の「最下層」からのデータである。
      // ----
      RS_XmlReader :: iterator ii
          = this -> find ( * ( _obj_class . rbegin () ) ) ;
      if ( ii == this -> end () )
      {
        cerr << "Error: tag <create> without class initialiser \""
             << * _obj_class . rbegin () << "\"." << endl ;
        exit ( 3 ) ;
      }
      _obj_class . pop_back () ;
      RS_ObjectContainer * _yoc = ii -> second ;

      // ----
      // Find the RS_ObjectContainer containing the "related object" using
      // the data obtained from the <under> tag.
      // <under> タグから取り出されたデータを用い，関連オブジェクトを格納す
      // る RS_ObjectContainer を取り出す。
      // ----
      while ( _obj_class . size () ) 
      {
        RS_Object * _yoj = _yoc -> locate ( * ( _obj_name . rbegin () ) ) ;
        if ( ! _yoj )
        {
          cerr << "Error: could not find the object with name \""
               << * _obj_name . rbegin () << "\"." << endl ;
          exit ( 3 ) ;
        }
        _yoc = _yoj -> getContainer ( * ( _obj_class . rbegin () ) ) ;
        if ( ! _yoc )
        {
          cerr << "Error: could not find the object container of class \""
               << * _obj_class . rbegin () << "\" in object named \""
               << * _obj_name . rbegin () << "\"." << endl ;
          exit ( 2 ) ;
        }
        _obj_name . pop_back () ;
        _obj_class . pop_back () ;
      }

      // ----
      // Find the pointer to the object. The last remaining string must
      // have been left in _obj_name.
      // オブジェクトへのポインタを得る。最後の文字列が _obj_name に残され
      // ているはずである。
      // ----
      if ( _obj_name . size () > 1 )
      {
        cerr << "Error: internal error 101, there must be only one "
             << "object name left" << endl ;
        exit ( 101 ) ;
      }
      if ( ! _obj_name . size () )
      {
        cerr << "Error: internal error 102, there must be only one "
             << "object name left" << endl ;
        exit ( 102 ) ;
      }
      typeobj . push_back ( _yoc -> locate ( * _obj_name . rbegin () ) ) ;
    }
  }
  return xoc -> create ( dtag , typestr , typeobj ) ;
}



/**************************************************************************
 * string getClassString ( const TiXmlNode * _n_in ) : Non-member function
 *  to find the class string.
 * _n_in : tag containing attribute "class".
 **************************************************************************/
string
getClassString
( TiXmlNode const * _n_in )
{
  TiXmlAttribute const * attr = _n_in -> ToElement () -> FirstAttribute () ;
  bool sw_class = false ;
  string ret_val ;
  while ( attr )
  {
    if ( string ( attr -> Name () ) == "class" )
    {
      // Attribute "class"
      if ( sw_class )
      {
        // Strange ... two attributes of same name?
        // 同じ属性が2つある？... エラー
        cerr << "Error: two \"class\" attributes?" << endl ;
        exit ( 2 ) ;
      }
      ret_val = attr -> ValueStr () ;
      sw_class = true ;
    }
    attr = attr -> Next () ;
  }
  if ( ! sw_class )
  {
    cerr << "Error: tag <" << _n_in -> ValueStr ()
         << "> did not contain attribute \"class\"." << endl ;
    exit ( 1 ) ;
  }
  return ret_val ;
}



/**************************************************************************
 * string getNameString ( const TiXmlNode * _n_in ) : Non-member function
 *  to find the name string.
 * _n_in : tag containing attribute "name".
 **************************************************************************/
string
getNameString
( TiXmlNode const * _n_in )
{
  TiXmlAttribute const * attr = _n_in -> ToElement () -> FirstAttribute () ;
  bool sw_name = false ;
  string ret_val ;
  while ( attr )
  {
    if ( string ( attr -> Name () ) == "name" )
    {
      // Attribute "name"
      if ( sw_name )
      {
        // Strange ... two attributes of same name?
        // 同じ属性が2つある？... エラー
        cerr << "Error: two \"name\" attributes?" << endl ;
        exit ( 2 ) ;
      }
      ret_val = attr -> ValueStr () ;
      sw_name = true ;
    }
    attr = attr -> Next () ;
  }
  if ( ! sw_name )
  {
    cerr << "Error: tag <" << _n_in -> ValueStr ()
         << "> did not contain attribute \"name\"." << endl ;
    exit ( 1 ) ;
  }
  return ret_val ;
}
