/***************************************************************************
 *  aGrUM modified frames and atg files for cocoR
 *   Copyright (c) 2005 by Christophe GONZALES and Pierre-Henri WUILLEMIN  *
 *   {prenom.nom}_at_lip6.fr
***************************************************************************/
/*----------------------------------------------------------------------
Compiler Generator Coco/R,
Copyright (c) 1990, 2004 Hanspeter Moessenboeck, University of Linz
extended by M. Loeberbauer & A. Woess, Univ. of Linz
ported to C++ by Csaba Balazs, University of Szeged
with improvements by Pat Terry, Rhodes University

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

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.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

As an exception, it is allowed to write an extension of Coco/R that is
used as a plugin in non-free software.

If not otherwise stated, any source code generated by Coco/R (other than
Coco/R itself) does not fall under the GNU General Public License.
-----------------------------------------------------------------------*/


#include <memory.h>
#include <string.h>
#include "Scanner.h"
#include <agrum/core/cocoR/common.h>

namespace gum {
namespace DSL {


Token::Token() {
  kind = 0;
  pos  = 0;
  col  = 0;
  line = 0;
  val  = nullptr;
  next = nullptr;
}

Token::~Token() {
  coco_string_delete( val );
}

Buffer::Buffer( FILE* s, bool isUserStream ) {
// ensure binary read on windows
#if _MSC_VER >= 1300
  _setmode( _fileno( s ), _O_BINARY );
#endif
  stream = s; this->isUserStream = isUserStream;

  if ( CanSeek() ) {
    fseek( s, 0, SEEK_END );
    fileLen = ftell( s );
    fseek( s, 0, SEEK_SET );
    bufLen = ( fileLen < MAX_BUFFER_LENGTH ) ? fileLen : MAX_BUFFER_LENGTH;
    bufStart = INT_MAX; // nothing in the buffer so far
  } else {
    fileLen = bufLen = bufStart = 0;
  }

  bufCapacity = ( bufLen>0 ) ? bufLen : MIN_BUFFER_LENGTH;
  buf = new unsigned char[bufCapacity];

  if ( fileLen > 0 ) SetPos( 0 );      // setup  buffer to position 0 (start)
  else bufPos = 0; // index 0 is already after the file, thus Pos = 0 is invalid

  if ( bufLen == fileLen && CanSeek() ) Close();
}

Buffer::Buffer( Buffer* b ) {
  buf = b->buf;
  bufCapacity = b->bufCapacity;
  b->buf = nullptr;
  bufStart = b->bufStart;
  bufLen = b->bufLen;
  fileLen = b->fileLen;
  bufPos = b->bufPos;
  stream = b->stream;
  b->stream = nullptr;
  isUserStream = b->isUserStream;
}

Buffer::Buffer( const unsigned char* buf, int len ) {
  this->isUserStream = false;
  this->buf = new unsigned char[len];
  memcpy( this->buf, buf, len*sizeof( unsigned char ) );
  bufStart = 0;
  bufCapacity = bufLen = len;
  fileLen = len;
  bufPos = 0;
  stream = nullptr;
}

Buffer::~Buffer() {
  Close();

  if ( buf != nullptr ) {
    delete [] buf;
    buf = nullptr;
  }
}

void Buffer::Close() {
  if ( !isUserStream && stream != nullptr ) {
    fclose( stream );
    stream = nullptr;
  }
}

int Buffer::GetPercent() {
  return ( int )( ( 100.0*GetPos() )/fileLen );
}

int Buffer::Read() {
  if ( bufPos < bufLen ) {
    return buf[bufPos++];
  } else if ( GetPos() < fileLen ) {

    SetPos( GetPos() ); // shift buffer start to Pos
    return buf[bufPos++];
  } else if ( ( stream != nullptr ) && !CanSeek() && ( ReadNextStreamChunk() > 0 ) ) {
    return buf[bufPos++];
  } else {
    return EoF;
  }
}

int Buffer::Peek() {
  int curPos = GetPos();
  int ch = Read();
  SetPos( curPos );
  return ch;
}

// beg .. begin, zero-based, inclusive, in byte
// end .. end, zero-based, exclusive, in byte
wchar_t* Buffer::GetString( int beg, int end ) {
  int len = 0;
  wchar_t* buf = new wchar_t[end - beg];
  int oldPos = GetPos();
  SetPos( beg );

  while ( GetPos() < end ) buf[len++] = ( wchar_t ) Read();

  SetPos( oldPos );
  wchar_t* res = coco_string_create( buf, 0, len );
  coco_string_delete( buf );
  return res;
}

int Buffer::GetPos() {
  return bufPos + bufStart;
}

void Buffer::SetPos( int value ) {
  if ( ( value >= fileLen ) && ( stream != nullptr ) && !CanSeek() ) {
    // Wanted position is after buffer and the stream
    // is not seek-able e.g. network or console,
    // thus we have to read the stream manually till
    // the wanted position is in sight.
    while ( ( value >= fileLen ) && ( ReadNextStreamChunk() > 0 ) );
  }

  if ( ( value < 0 ) || ( value > fileLen ) ) {
    wprintf( L"--- buffer out of bounds access, position: %d\n", value );
    exit( 1 );
  }

  if ( ( value >= bufStart ) && ( value < ( bufStart + bufLen ) ) ) { // already in buffer
    bufPos = value - bufStart;
  } else if ( stream != nullptr ) { // must be swapped in
    fseek( stream, value, SEEK_SET );
    bufLen = (int)fread( buf, int(sizeof( unsigned char )), bufCapacity, stream );
    bufStart = value; bufPos = 0;
  } else {
    bufPos = fileLen - bufStart; // make Pos return fileLen
  }
}

// Read the next chunk of bytes from the stream, increases the buffer
// if needed and updates the fields fileLen and bufLen.
// Returns the number of bytes read.
int Buffer::ReadNextStreamChunk() {
  int free = bufCapacity - bufLen;

  if ( free == 0 ) {
    // in the case of a growing input stream
    // we can neither seek in the stream, nor can we
    // foresee the maximum length, thus we must adapt
    // the buffer size on demand.
    bufCapacity = bufLen * 2;
    unsigned char* newBuf = new unsigned char[bufCapacity];
    memcpy( newBuf, buf, bufLen*sizeof( unsigned char ) );
    delete [] buf;
    buf = newBuf;
    free = bufLen;
  }

  int read = (int)fread( buf + bufLen, int(sizeof( unsigned char )), free, stream );

  if ( read > 0 ) {
    fileLen = bufLen = ( bufLen + read );
    return read;
  }

  // end of stream reached
  return 0;
}

bool Buffer::CanSeek() {
  return ( stream != nullptr ) && ( ftell( stream ) != -1 );
}

int UTF8Buffer::Read() {
  int ch;

  do {
    ch = Buffer::Read();
    // until we find a utf8 start (0xxxxxxx or 11xxxxxx)
  } while ( ( ch >= 128 ) && ( ( ch & 0xC0 ) != 0xC0 ) && ( ch != EoF ) );

  if ( ch < 128 || ch == EoF ) {
    // nothing to do, first 127 chars are the same in ascii and utf8
    // 0xxxxxxx or end of file character
  } else if ( ( ch & 0xF0 ) == 0xF0 ) {
    // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    int c1 = ch & 0x07; ch = Buffer::Read();
    int c2 = ch & 0x3F; ch = Buffer::Read();
    int c3 = ch & 0x3F; ch = Buffer::Read();
    int c4 = ch & 0x3F;
    ch = ( ( ( ( ( c1 << 6 ) | c2 ) << 6 ) | c3 ) << 6 ) | c4;
  } else if ( ( ch & 0xE0 ) == 0xE0 ) {
    // 1110xxxx 10xxxxxx 10xxxxxx
    int c1 = ch & 0x0F; ch = Buffer::Read();
    int c2 = ch & 0x3F; ch = Buffer::Read();
    int c3 = ch & 0x3F;
    ch = ( ( ( c1 << 6 ) | c2 ) << 6 ) | c3;
  } else if ( ( ch & 0xC0 ) == 0xC0 ) {
    // 110xxxxx 10xxxxxx
    int c1 = ch & 0x1F; ch = Buffer::Read();
    int c2 = ch & 0x3F;
    ch = ( c1 << 6 ) | c2;
  }

  return ch;
}

Scanner::Scanner(const unsigned char* buf, int len, std::string filename, bool trace) {
  buffer = new Buffer( buf, len );
  __filenamne=widen( filename.c_str() );
  __trace=trace;
  Init();
}

Scanner::Scanner( const char* fileName,bool trace ) {
  Load( widen( std::string( fileName ) ).c_str() );
  __trace=trace;
}

Scanner::Scanner( const wchar_t* fileName,bool trace ) {
  Load( fileName );
  __trace=trace;
}

void Scanner::Load( const wchar_t* fileName ) {
  FILE* stream;
  char* chFileName = coco_string_create_char( fileName );

  if ( ( stream = fopen( chFileName, "rb" ) ) == nullptr ) {
    std::string s( "No such file : " ); s+=chFileName;
    GUM_ERROR( gum::IOError,s );
  }

  coco_string_delete( chFileName );
  buffer = new Buffer( stream, false );
  __filenamne=std::wstring( fileName );
  Init();
}

Scanner::Scanner( FILE* s,bool trace ) {
  buffer = new Buffer( s, true );
  __filenamne=L"FILE";
  Init();
  __trace=trace;
}

Scanner::~Scanner() {
  char* cur = ( char* ) firstHeap;

  while ( cur != nullptr ) {
    cur = *( char** )( cur + HEAP_BLOCK_SIZE );
    free( firstHeap );
    firstHeap = cur;
  }

  if ( tval ) delete [] tval;

  if ( buffer ) delete buffer;
}

void Scanner::Init() {
  percent=-1;
  EOL    = '\n';
  eofSym = 0;
  	maxT = 33;
	noSym = 33;
	int i;
	for (i = 65; i <= 90; ++i) start.set(i, 1);
	for (i = 95; i <= 95; ++i) start.set(i, 1);
	for (i = 97; i <= 122; ++i) start.set(i, 1);
	for (i = 48; i <= 57; ++i) start.set(i, 15);
	for (i = 43; i <= 43; ++i) start.set(i, 16);
	for (i = 45; i <= 45; ++i) start.set(i, 16);
	start.set(34, 11);
	start.set(61, 17);
	start.set(123, 19);
	start.set(125, 20);
	start.set(59, 21);
	start.set(40, 22);
	start.set(41, 23);
	start.set(44, 24);
	start.set(124, 25);
		start.set(Buffer::EoF, -1);
	keywords.set(L"net", 6);
	keywords.set(L"NUMSAMPLES", 10);
	keywords.set(L"BKCOLOR", 11);
	keywords.set(L"SHOWAS", 12);
	keywords.set(L"node", 13);
	keywords.set(L"TYPE", 14);
	keywords.set(L"SCREEN", 15);
	keywords.set(L"USER_PROPERTIES", 16);
	keywords.set(L"DOCUMENTATION", 17);
	keywords.set(L"EXTRA_DEFINITION", 18);
	keywords.set(L"OBSERVATION_COST", 19);
	keywords.set(L"HEADER", 20);
	keywords.set(L"CREATION", 21);
	keywords.set(L"WINDOWPOSITION", 22);
	keywords.set(L"ID", 23);
	keywords.set(L"NAME", 24);
	keywords.set(L"PARENTS", 25);
	keywords.set(L"DEFINITION", 29);
	keywords.set(L"NAMESTATES", 30);
	keywords.set(L"PROBABILITIES", 31);


  tvalLength = 128;
  tval = new wchar_t[tvalLength]; // text of current token

  // HEAP_BLOCK_SIZE byte heap + pointer to next heap block
  heap = malloc( HEAP_BLOCK_SIZE + sizeof( void* ) );
  firstHeap = heap;
  heapEnd = ( void** )( ( ( char* ) heap ) + HEAP_BLOCK_SIZE );
  *heapEnd = 0;
  heapTop = heap;

  if ( sizeof( Token ) > HEAP_BLOCK_SIZE ) {
    wprintf( L"--- Too small HEAP_BLOCK_SIZE\n" );
    exit( 1 );
  }

  pos = -1; line = 1; col = 0; charPos = -1;
  oldEols = 0;
  NextCh();

  if ( ch == 0xEF ) { // check optional byte order mark for UTF-8
    NextCh(); int ch1 = ch;
    NextCh(); int ch2 = ch;

    if ( ch1 != 0xBB || ch2 != 0xBF ) {
      wprintf( L"Illegal byte order mark at start of file" );
      exit( 1 );
    }

    Buffer* oldBuf = buffer;
    buffer = new UTF8Buffer( buffer ); col = 0; charPos = -1;
    delete oldBuf; oldBuf = nullptr;
    NextCh();
  }

  
  pt = tokens = CreateToken(); // first token is a dummy
}

void Scanner::NextCh() {
  if ( oldEols > 0 ) { ch = EOL; oldEols--; }
  else {
    pos = buffer->GetPos();
    ch = buffer->Read();
    int p=buffer->GetPercent();

    if ( ch==Buffer::EoF ) {
      GUM_EMIT1( onLoad,200 );
    } else {
      if ( percent<p ) {
        percent=p;
        GUM_EMIT1( onLoad,percent );
      }
    }

    col++; charPos++;

    // replace isolated '\r' by '\n' in order to make
    // eol handling uniform across Windows, Unix and Mac
    if ( ch == L'\r' && buffer->Peek() != L'\n' ) ch = EOL;

    if ( ch == EOL ) { /*if (__trace) std::cout<<line<<std::endl;*/ line++; col = 0; }
  }

  
}

void Scanner::AddCh() {
  if ( tlen >= tvalLength ) {
    tvalLength *= 2;
    wchar_t* newBuf = new wchar_t[tvalLength];
    memcpy( newBuf, tval, tlen*sizeof( wchar_t ) );
    delete [] tval;
    tval = newBuf;
  }

  if ( ch != Buffer::EoF ) {
    		tval[tlen++] = ch;
    NextCh();
  }
}


bool Scanner::Comment0() {
	int level = 1, pos0 = pos, line0 = line, col0 = col, charPos0 = charPos;
	NextCh();
	if (ch == L'/') {
		NextCh();
		for(;;) {
			if (ch == 10) {
				level--;
				if (level == 0) { oldEols = line - line0; NextCh(); return true; }
				NextCh();
			} else if (ch == buffer->EoF) return false;
			else NextCh();
		}
	} else {
		buffer->SetPos(pos0); NextCh(); line = line0; col = col0; charPos = charPos0;
	}
	return false;
}

bool Scanner::Comment1() {
	int level = 1, pos0 = pos, line0 = line, col0 = col, charPos0 = charPos;
	NextCh();
	if (ch == L'*') {
		NextCh();
		for(;;) {
			if (ch == L'*') {
				NextCh();
				if (ch == L'/') {
					level--;
					if (level == 0) { oldEols = line - line0; NextCh(); return true; }
					NextCh();
				}
			} else if (ch == L'/') {
				NextCh();
				if (ch == L'*') {
					level++; NextCh();
				}
			} else if (ch == buffer->EoF) return false;
			else NextCh();
		}
	} else {
		buffer->SetPos(pos0); NextCh(); line = line0; col = col0; charPos = charPos0;
	}
	return false;
}


void Scanner::CreateHeapBlock() {
  void* newHeap;
  char* cur = ( char* ) firstHeap;

  while ( ( ( char* ) tokens < cur ) || ( ( char* ) tokens > ( cur + HEAP_BLOCK_SIZE ) ) ) {
    cur = *( ( char** )( cur + HEAP_BLOCK_SIZE ) );
    free( firstHeap );
    firstHeap = cur;
  }

  // HEAP_BLOCK_SIZE byte heap + pointer to next heap block
  newHeap = malloc( HEAP_BLOCK_SIZE + sizeof( void* ) );
  *heapEnd = newHeap;
  heapEnd = ( void** )( ( ( char* ) newHeap ) + HEAP_BLOCK_SIZE );
  *heapEnd = 0;
  heap = newHeap;
  heapTop = heap;
}

Token* Scanner::CreateToken() {
  Token* t;

  if ( ( ( char* ) heapTop + ( int ) sizeof( Token ) ) >= ( char* ) heapEnd ) {
    CreateHeapBlock();
  }

  t = ( Token* ) heapTop;
  heapTop = ( void* )( ( char* ) heapTop + sizeof( Token ) );
  t->val = nullptr;
  t->next = nullptr;
  return t;
}

void Scanner::AppendVal( Token* t ) {
  int reqMem = ( tlen + 1 ) * sizeof( wchar_t );

  if ( ( ( char* ) heapTop + reqMem ) >= ( char* ) heapEnd ) {
    if ( reqMem > HEAP_BLOCK_SIZE ) {
      wprintf( L"--- Too long token value\n" );
      exit( 1 );
    }

    CreateHeapBlock();
  }

  t->val = ( wchar_t* ) heapTop;
  heapTop = ( void* )( ( char* ) heapTop + reqMem );

  wcsncpy( t->val, tval, tlen );
  t->val[tlen] = L'\0';
}

Token* Scanner::NextToken() {
  while ( ch == ' ' ||
          			(ch >= 9 && ch <= 10) || ch == 13 || ch == L'='
        ) NextCh();

  	if ((ch == L'/' && Comment0()) || (ch == L'/' && Comment1())) return NextToken();
  int recKind = noSym;
  int recEnd = pos;
  t = CreateToken();
  t->pos = pos; t->col = col; t->line = line; t->charPos = charPos;
  int state = start.state( ch );
  tlen = 0; AddCh();

  switch ( state ) {
    case -1: { t->kind = eofSym; break; } // NextCh already done

    case 0: {
case_0:

      if ( recKind != noSym ) {
        tlen = recEnd - t->pos;
        SetScannerBehindT();
      }

      t->kind = recKind; break;
    } // NextCh already done

    		case 1:
			case_1:
			recEnd = pos; recKind = 1;
			if (ch == L'%' || (ch >= L'-' && ch <= L'.') || (ch >= L'0' && ch <= L'9') || ch == L'=' || ch == L'?' || (ch >= L'A' && ch <= L'Z') || ch == L'_' || (ch >= L'a' && ch <= L'z')) {AddCh(); goto case_1;}
			else {t->kind = 1; wchar_t *literal = coco_string_create(tval, 0, tlen); t->kind = keywords.get(literal, t->kind); coco_string_delete(literal); break;}
		case 2:
			case_2:
			recEnd = pos; recKind = 1;
			if (ch == L'%' || (ch >= L'-' && ch <= L'.') || (ch >= L'0' && ch <= L'9') || ch == L'=' || ch == L'?' || (ch >= L'A' && ch <= L'Z') || ch == L'_' || (ch >= L'a' && ch <= L'z')) {AddCh(); goto case_2;}
			else {t->kind = 1; wchar_t *literal = coco_string_create(tval, 0, tlen); t->kind = keywords.get(literal, t->kind); coco_string_delete(literal); break;}
		case 3:
			case_3:
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_4;}
			else {goto case_0;}
		case 4:
			case_4:
			recEnd = pos; recKind = 3;
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_4;}
			else if (ch == L'E' || ch == L'e') {AddCh(); goto case_5;}
			else {t->kind = 3; break;}
		case 5:
			case_5:
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_7;}
			else if (ch == L'+' || ch == L'-') {AddCh(); goto case_6;}
			else {goto case_0;}
		case 6:
			case_6:
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_7;}
			else {goto case_0;}
		case 7:
			case_7:
			recEnd = pos; recKind = 3;
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_7;}
			else {t->kind = 3; break;}
		case 8:
			case_8:
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_10;}
			else if (ch == L'+' || ch == L'-') {AddCh(); goto case_9;}
			else {goto case_0;}
		case 9:
			case_9:
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_10;}
			else {goto case_0;}
		case 10:
			case_10:
			recEnd = pos; recKind = 3;
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_10;}
			else {t->kind = 3; break;}
		case 11:
			case_11:
			if (ch <= L'!' || (ch >= L'#' && ch <= 65535)) {AddCh(); goto case_11;}
			else if (ch == L'"') {AddCh(); goto case_12;}
			else {goto case_0;}
		case 12:
			case_12:
			{t->kind = 4; break;}
		case 13:
			case_13:
			if (ch <= L':' || (ch >= L'<' && ch <= 65535)) {AddCh(); goto case_13;}
			else if (ch == L';') {AddCh(); goto case_14;}
			else {goto case_0;}
		case 14:
			case_14:
			{t->kind = 5; break;}
		case 15:
			case_15:
			recEnd = pos; recKind = 2;
			if (ch == L'=' || (ch >= L'A' && ch <= L'D') || (ch >= L'F' && ch <= L'Z') || ch == L'_' || (ch >= L'a' && ch <= L'd') || (ch >= L'f' && ch <= L'z')) {AddCh(); goto case_2;}
			else if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_15;}
			else if (ch == L'.') {AddCh(); goto case_3;}
			else if (ch == L'E' || ch == L'e') {AddCh(); goto case_8;}
			else {t->kind = 2; break;}
		case 16:
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_18;}
			else {goto case_0;}
		case 17:
			case_17:
			recEnd = pos; recKind = 1;
			if (ch == L'%' || (ch >= L'-' && ch <= L'.') || (ch >= L'0' && ch <= L'9') || ch == L'=' || ch == L'?' || (ch >= L'A' && ch <= L'Z') || ch == L'_' || (ch >= L'a' && ch <= L'z')) {AddCh(); goto case_17;}
			else if (ch <= L'$' || (ch >= L'&' && ch <= L',') || ch == L'/' || ch == L':' || ch == L'<' || ch == L'>' || ch == L'@' || (ch >= L'[' && ch <= L'^') || ch == L'`' || (ch >= L'{' && ch <= 65535)) {AddCh(); goto case_13;}
			else if (ch == L';') {AddCh(); goto case_14;}
			else {t->kind = 1; wchar_t *literal = coco_string_create(tval, 0, tlen); t->kind = keywords.get(literal, t->kind); coco_string_delete(literal); break;}
		case 18:
			case_18:
			recEnd = pos; recKind = 2;
			if ((ch >= L'0' && ch <= L'9')) {AddCh(); goto case_18;}
			else if (ch == L'.') {AddCh(); goto case_3;}
			else if (ch == L'E' || ch == L'e') {AddCh(); goto case_8;}
			else {t->kind = 2; break;}
		case 19:
			{t->kind = 7; break;}
		case 20:
			{t->kind = 8; break;}
		case 21:
			{t->kind = 9; break;}
		case 22:
			{t->kind = 26; break;}
		case 23:
			{t->kind = 27; break;}
		case 24:
			{t->kind = 28; break;}
		case 25:
			{t->kind = 32; break;}

  }

  AppendVal( t );
  return t;
}

void Scanner::SetScannerBehindT() {
  buffer->SetPos( t->pos );
  NextCh();
  line = t->line; col = t->col; charPos = t->charPos;

  for ( int i = 0; i < tlen; i++ ) NextCh();
}

// get the next token (possibly a token already seen during peeking)
Token* Scanner::Scan() {
  if ( tokens->next == nullptr ) {
    return pt = tokens = NextToken();
  } else {
    pt = tokens = tokens->next;
    return tokens;
  }
}

// peek for the next token, ignore pragmas
Token* Scanner::Peek() {
  do {
    if ( pt->next == nullptr ) {
      pt->next = NextToken();
    }

    pt = pt->next;
  } while ( pt->kind > maxT ); // skip pragmas

  return pt;
}

// make sure that peeking starts at the current scan position
void Scanner::ResetPeek() {
  pt = tokens;
}

} // namespace
} // namespace



