// =====================================================================
//  $Id: TTerminalUserInterface.cc,v 1.7 2004/06/24 15:15:25 goiwai Exp $
//  $Name: CLDAQ-1-14-03 $
//  $Log: TTerminalUserInterface.cc,v $
//  Revision 1.7  2004/06/24 15:15:25  goiwai
//  たくさんの変更
//  Tcout,Tcerr,Tlogをバッファ経由にした。
//  何個かexternした。Tlog,Tcout,Tcerr,Tinfo...Tcritなど出力関係。
//
//  Revision 1.6  2004/03/07 10:30:31  goiwai
//  ROOTに組みこむためのおまじないマクロを埋めこみました。
//  全てにおいて完全に動作するわけではありません。
//
//  Revision 1.5  2003/10/06 17:02:40  goiwai
//  *** empty log message ***
//
//  Revision 1.4  2003/07/30 16:18:52  goiwai
//  ファイルにコミットログをつけることにしました.
//
// =====================================================================
#include "TTerminalUserInterface.hh"
#include "TRunManager.hh"
#include "TCommand.hh"

static const Tchar _asciiCtrlA = '\001';
static const Tchar _asciiCtrlB = '\002';
static const Tchar _asciiCtrlD = '\004';
static const Tchar _asciiCtrlE = '\005';
static const Tchar _asciiCtrlF = '\006';
static const Tchar _asciiCtrlK = '\013';
static const Tchar _asciiCtrlL = '\014';
static const Tchar _asciiCtrlN = '\016';
static const Tchar _asciiCtrlP = '\020';
static const Tchar _asciiCtrlY = '\031';
static const Tchar _asciiCtrlZ = '\032';
static const Tchar _asciiTAB = '\011';
static const Tchar _asciiBS = '\010';
static const Tchar _asciiDEL = '\177';
static const Tchar _asciiESC = '\033';

TTerminalUserInterface::TTerminalUserInterface( const Tstring& prompt, const Tstring& history )
  : TUserInterface( history ), theCommandHistoryIndex( 0 ),
    theCursorPosition( 0 ), thePrompt( prompt ), theAcceptString(),
    theStringBuffer(), theCommandBuffer()
{
  tcgetattr( 0, &theTerminal );
  setupterm( 0, 1, 0 );
}

TTerminalUserInterface::~TTerminalUserInterface()
{;}

Tvoid TTerminalUserInterface::initializeCommandLine()
{
  theCommandHistoryIndex = (Tint)theCommandHistory.size();
  theCursorPosition = 0;
  theAcceptString.erase();
  theCommandBuffer.erase();
  std::cout << thePrompt << std::flush;
  return;
}

const Tstring& TTerminalUserInterface::readLine()
{
  setTerminalInputMode();
  initializeCommandLine();

  Tchar cc;

  do {
    Tcin.get( cc );

    switch ( cc ) {

      case _asciiCtrlA:
	moveCursorTop();
	break;

      case _asciiCtrlB:
	backwardCursor();
	break;

      case _asciiCtrlD:
	deleteCharacter();
	break;

      case _asciiCtrlE:
	moveCursorEnd();
	break;

      case _asciiCtrlF:
	forwardCursor();
	break;

      case _asciiCtrlK:
	cutCharacter();
	break;

      case _asciiCtrlL:
	clearScreen();
	break;

      case _asciiCtrlN:
	nextCommand();
	break;

      case _asciiCtrlP:
	previousCommand();
	break;

      case _asciiCtrlY:
	pasteCharacter();
	break;

      case _asciiCtrlZ:
	suspendTerminal();
	break;

      case _asciiTAB:
	completeCommand();
	break;

      case _asciiBS:
	backspaceCharacter();
	break;

      case _asciiDEL:
	deleteCharacter();
	break;

      default:
	break;
    }

    if ( cc == _asciiESC ) {
      Tcin.get( cc );
      if ( cc == '[' ) {
	Tcin.get( cc );
	switch ( cc ) {
	  case 'A':  // up
	    cc = 'P' - '@';
	    previousCommand();
	    break;
	  case 'B':  // down
	    cc = 'N' - '@';
	    nextCommand();
	    break;
	  case 'C':  // right
	    cc = 'F' - '@';
	    forwardCursor();
	    break;
	  case 'D':  // left
	    cc = 'B' - '@';
	    backwardCursor();
	    break;
	  default:
	    cc = 0;
	    break;
	}
      }
    }
    
    insertCharacter( cc );

  } while ( cc != Teol );

  
  if ( theAcceptString.size() > 0 ) {

    // erase space character form back
    while ( !( theAcceptString.empty() ) && ( theAcceptString.rfind( Tspace ) == theAcceptString.size() - 1 ) ) {
      theAcceptString.erase( theAcceptString.end() - 1 );
    }

    // erase space character
    while ( !( theAcceptString.empty() ) && ( theAcceptString.find( Tspace ) == 0 ) ) {
      theAcceptString.erase( theAcceptString.begin() );
    }
  }
  std::cout << std::endl;

  resetTerminal();

  return theAcceptString;
}

Tvoid TTerminalUserInterface::insertCharacter( Tchar cc )
{
  if ( ! isprint( cc ) ) {
    return;
  }

  std::cout << cc;
  for ( Tint i = theCursorPosition; i < (Tint)theAcceptString.size(); i ++ ) {
    std::cout << theAcceptString[ i ];
  }
  for ( Tint i = theCursorPosition; i < (Tint)theAcceptString.size(); i ++ ) {
    std::cout << _asciiBS;
  }
  std::cout << std::flush;
    
  if ( isCursorEnd() ) {
    theAcceptString += cc;
  } else {
    theAcceptString.insert( theAcceptString.begin() + theCursorPosition, cc );
  }

  theCursorPosition ++;
  return;
}

Tvoid TTerminalUserInterface::backspaceCharacter()
{
  if ( isCursorTop() ) {
    return;
  }

  if ( isCursorEnd() ) {
    std::cout << _asciiBS << Tspace << _asciiBS;
  } else {
    std::cout << _asciiBS;
    for ( Tint i = theCursorPosition; i < (Tint)theAcceptString.size(); i ++ ) {
      std::cout << theAcceptString[ i ];
    }
    std::cout << Tspace;
    for ( Tint i = theCursorPosition; i < (Tint)( theAcceptString.size() + 1 ); i ++ ) {
      std::cout << _asciiBS;
    }
  }
  std::cout << std::flush;
  theAcceptString.erase( theCursorPosition - 1, 1 );
  theCursorPosition --;
  return;
}

Tvoid TTerminalUserInterface::deleteCharacter()
{
  forwardCursor();
  backspaceCharacter();
  return;
}

Tvoid TTerminalUserInterface::clearLine()
{
  moveCursorTop();
  clearAfterCursor();
  return;
}

Tvoid TTerminalUserInterface::clearScreen()
{
  ClearScreen();
  if ( ! theAcceptString.empty() ) {
    Tint posbuf = theCursorPosition;
    theCursorPosition = theAcceptString.size();
    std::cout << thePrompt << theAcceptString;
    Tint nback = theCursorPosition - posbuf;
    if ( nback > 0 ) {
      for ( Tint i = 0; i < nback; i ++ ) {
	backwardCursor();
      }
    }
    return;
  }
  theCursorPosition = 0;
  std::cout << thePrompt << std::flush;
  return;
}

Tvoid TTerminalUserInterface::clearAfterCursor()
{
  if ( isCursorEnd() ) {
    return;
  }
  for ( Tint i = theCursorPosition; i < (Tint)theAcceptString.size(); i ++ ) {
    std::cout << Tspace;
  }
  for ( Tint i = (Tint)theAcceptString.size(); i > theCursorPosition; i -- ) {
    std::cout << _asciiBS;
  }
  std::cout << std::flush;
  theAcceptString.erase( theCursorPosition, theAcceptString.size() - theCursorPosition );
  return;
}

Tvoid TTerminalUserInterface::forwardCursor()
{
  if ( isCursorEnd() ) {
    return;
  }
  std::cout << theAcceptString[ (Tsize_t)( theCursorPosition ) ] << std::flush;
  theCursorPosition ++;
  return;
}

Tvoid TTerminalUserInterface::backwardCursor()
{
  if ( isCursorTop() ) {
    return;
  }
  std::cout << _asciiBS << std::flush;
  theCursorPosition --;
  return;
}

Tvoid TTerminalUserInterface::moveCursorTop()
{
  while ( isCursorTop() == Tfalse ) {
    backwardCursor();
  }
  return;
}

Tvoid TTerminalUserInterface::moveCursorEnd()
{
  while ( isCursorEnd() == Tfalse ) {
    forwardCursor();
  }
  return;
}

Tvoid TTerminalUserInterface::cutCharacter()
{
  if ( !( theStringBuffer.empty() ) ) {
    theStringBuffer.erase();
  }
  for ( Tint i = theCursorPosition; i < (Tint)theAcceptString.size(); i ++ ) {
    theStringBuffer += theAcceptString[ i ];
  }
  Tint nback = (Tint)( theAcceptString.size() ) - theCursorPosition;
  moveCursorEnd();
  for ( Tint i = 0; i < nback; i ++ ) {
    backspaceCharacter();
  }
  return;
}

Tvoid TTerminalUserInterface::pasteCharacter()
{
  if ( theStringBuffer.empty() ) {
    return;
  }
  for ( int i = 0; i < (Tint)theStringBuffer.size(); i ++ ) {
    insertCharacter( theStringBuffer[ i ] );
  }
  return;
}

Tvoid TTerminalUserInterface::nextCommand()
{
  if ( theCommandHistoryIndex > (Tint)theCommandHistory.size() - 1 ) {
    return;
  }
  clearLine();
  Tstring com;
  if ( theCommandHistoryIndex == (Tint)theCommandHistory.size() - 1 ) {
    com = theCommandBuffer;
  } else {
    com = theCommandHistory[ theCommandHistoryIndex + 1 ];
  }

  for ( int i = 0; i < (Tint)com.size(); i ++ ) {
    insertCharacter( com[ i ] );
  }
  theCommandHistoryIndex ++;
  return;
}

Tvoid TTerminalUserInterface::previousCommand()
{
  if ( theCommandHistory.empty() || theCommandHistoryIndex == 0 ) {
    return;
  }

  if ( theCommandHistoryIndex == (Tint)theCommandHistory.size() ) {
    theCommandBuffer = theAcceptString;
  }

  clearLine();

  Tstring com = theCommandHistory[ theCommandHistoryIndex - 1 ];
  for ( int i = 0; i < (Tint)com.size(); i ++ ) {
    insertCharacter( com[ i ] );
  }
  theCommandHistoryIndex --;
  return;
}

Tvoid TTerminalUserInterface::completeCommand()
{
  Tstring strbuf = theAcceptString;
  TstringList input = divide( strbuf );

  if ( input.empty() ) {
    complete();
  } else if ( input.size() == 1 && strbuf[ strbuf.size() - 1 ] != ' ' ) {
    complete( input[ 0 ] );
  } else if ( input.size() == 1 && input[ 0 ] == "cd" && strbuf[ strbuf.size() - 1 ] == ' ' ) {
    completeDirectory();
  } else if ( input.size() == 2 && input[ 0 ] == "cd" && strbuf[ strbuf.size() - 1 ] != ' ' ) {
    completeDirectory( input[ 1 ] );
  } else if ( input.size() > 1 && strbuf[ strbuf.size() - 1 ] != ' ' ) {
    complete( input );
  } else if ( input.size() > 0 && strbuf[ strbuf.size() - 1 ] == ' ' ) {
    complete();
  }

  return;
}

Tvoid TTerminalUserInterface::complete()
{
  std::cout << std::endl;
  ExecuteCommand( "ls" );

  if ( ! theAcceptString.empty() ) {
    Tint posbuf = theCursorPosition;
    theCursorPosition = theAcceptString.size();
    std::cout << thePrompt << theAcceptString;
    Tint nback = theCursorPosition - posbuf;
    if ( nback > 0 ) {
      for ( Tint i = 0; i < nback; i ++ ) {
	backwardCursor();
      }
    }
    return;
  }

  theCursorPosition = 0;
  std::cout << thePrompt << std::flush;

  return;
}

Tvoid TTerminalUserInterface::complete( const Tstring& input )
{
  // complete for command
  Tstring abspath = ModifyPath( input );
  TstringList candidate;
  for ( Tint i = 0; i < theCommandTable.GetSize(); i ++ ) {
    Tstring fullname = theCommandTable[ i ].GetFullName();
    if ( fullname.substr( 0, abspath.size() ) == abspath ) {
      candidate.push_back( fullname );
    } else if ( theCommandTable[ i ].IsBuiltinCommand() && fullname.substr( 0, input.size() ) == input ) {
      candidate.push_back( fullname );
    }
  }


  if ( candidate.empty() ) {
    return;
  }


  // 候補をフルパスから出力用に変更する
  // /run に居て ./sus の候補は /run/suspend だけど ./suspend に変換
  TstringList local = candidate;
  for ( Tsize_t i = 0; i < local.size(); i ++  ) {
    if ( abspath != "/" && input[ input.size() - 1 ] == '/' ) {
      local[ i ] = input.substr( 0, input.size() - 1 ) + local[ i ].substr( abspath.size(), local[ i ].size() - abspath.size() );
    } else {
      if ( candidate[ i ][ 0 ] == '/' ) {
	local[ i ] = input + local[ i ].substr( abspath.size(), local[ i ].size() - abspath.size() );
      }
    }
  }


  if ( local.size() == 1 ) {
    // 候補がひとつなので完全補間
    clearLine();
    theAcceptString = local[ 0 ];
    theAcceptString += ' ';
    theCursorPosition = theAcceptString.size();
    std::cout << theAcceptString;
  } else {

    for ( Tsize_t i = 0; i < candidate.size(); i ++  ) {
      if ( theCommandTable.GetCommandSpecified( candidate[ i ] ).IsAliasedCommand() ) {
 	local[ i ] += "@";
      } else {
 	local[ i ] += "*";
      }
    }

    theCommandTable.Sort( local );
    std::cout << std::endl;
    theCommandTable.List( GetNumberOfColumns(), local );

    // find out part of coincidence from candidate
    Tsize_t pos = input.size() - 1;
    Tbool coincidence = Ttrue;
    while ( coincidence ) {
      pos ++;
      for ( Tsize_t i = 0; i < local.size() - 1; i ++ ) {
 	coincidence &= ( ( local[ i ] )[ pos ] == ( local[ i + 1 ] )[ pos ] );
      }
    }
    
    theAcceptString = local[ 0 ];
    theAcceptString.erase( pos, ( theAcceptString.size() - pos ) );
    theCursorPosition = theAcceptString.size();
    std::cout << thePrompt << theAcceptString;

  }

  std::cout << std::flush;

  return;
}

Tvoid TTerminalUserInterface::complete( const TstringList& inputs )
{
  // 第2引数以上の補間
  // under construction
  // std::cout << "Tvoid TTerminalUserInterface::complete( const TstringList& inputs )" << std::endl;
  return;
}

Tvoid TTerminalUserInterface::completeDirectory()
{
  TstringList dirs = theCommandTable.GetDirectoryList( theCurrentWorkingDirectory );
  if ( dirs.empty() ) {
    return;
  }

  std::cout << std::endl;
  theCommandTable.List( GetNumberOfColumns(), theCommandTable.Sort( dirs ) );

  std::cout << thePrompt << theAcceptString;

  return;
}

Tvoid TTerminalUserInterface::completeDirectory( const Tstring& input )
{
  if ( input.size() >= 2 && input.substr( input.size() - 2, 2 ) == ".." ) {
    theAcceptString += "/";
    theCursorPosition += 1;
    std::cout << "/" << std::flush;
    return;
  }


  enum { DIRLIST, COMPLETE };
  Tint mode;
  if ( input[ input.size() - 1 ] == '/' ) {
    mode = DIRLIST;
  } else {
    mode = COMPLETE;
  }


  Tstring temppath = input;
  if ( temppath.size() > 2 && temppath.substr( 0, 2 ) == "./" ) {
    temppath.erase( 0, 2 );
  }
  Tstring abspath = ModifyPath( temppath );
  Tstring targetdir;
  Tsize_t pos = abspath.rfind( "/" );
  if ( input[ input.size() - 1 ] == '/' && theCommandTable.AlreadyExistDirectory( abspath ) ) {
    targetdir = abspath;
  } else if ( pos == 0 ) {
    targetdir = "/";
  } else {
    targetdir = abspath.substr( 0, pos );
  }

  TstringList dirlist = theCommandTable.GetDirectoryList( targetdir );
  TstringList candidate;
  if ( dirlist.empty() ) {
    return;
  } else if ( mode == DIRLIST ) {
    std::cout << std::endl;
    theCommandTable.Sort( dirlist );
    theCommandTable.List( GetNumberOfColumns(), dirlist );
    theCursorPosition = theAcceptString.size();
    std::cout << thePrompt << theAcceptString << std::flush;
    return;
  } else if ( mode == COMPLETE ) {
    Tsize_t pos = abspath.rfind( '/' );
    Tstring dir = abspath.substr( pos + 1, abspath.size() - pos - 1 );
    for ( Tsize_t i = 0; i < dirlist.size(); i ++ ) {
      if ( dirlist[ i ].substr( 0, dir.size() ) == dir ) {
 	candidate.push_back( dirlist[ i ] );
      }
    }

    if ( candidate.empty() ) {
      return;
    }
  } else {
    return;
  }



  if ( candidate.size() == 1 ) {
    // 候補がひとつなので完全補間
    clearLine();
    theAcceptString = "cd " + input.substr( 0, input.rfind( '/' ) + 1 ) + candidate[ 0 ];
    theCursorPosition = theAcceptString.size();
    std::cout << theAcceptString;
  } else {
    std::cout << std::endl;
    theCommandTable.Sort( candidate );
    theCommandTable.List( GetNumberOfColumns(), candidate );

    // find out part of coincidence from candidate
    Tsize_t pos = 0;
    Tbool coincidence = Ttrue;
    while ( coincidence ) {
      pos ++;
      for ( Tsize_t i = 0; i < candidate.size() - 1; i ++ ) {
	coincidence &= ( ( candidate[ i ] )[ pos ] == ( candidate[ i + 1 ] )[ pos ] );
      }
    }

    theAcceptString = candidate[ 0 ];
    theAcceptString.erase( pos, ( theAcceptString.size() - pos ) );
    theAcceptString = "cd " + input.substr( 0, input.rfind( '/' ) + 1 ) + theAcceptString;
    theCursorPosition = theAcceptString.size();
    std::cout << thePrompt << theAcceptString << std::flush;
  }

  return;
}

Tvoid TTerminalUserInterface::suspendTerminal() const
{
  pid_t pid = getpid();
  if ( fork() == 0 ) {
    exit( kill( pid, SIGSTOP ) );
  } else {
    wait( 0 );
  }
  return;
}

Tvoid TTerminalUserInterface::setTerminalInputMode()
{
  termios tiosbuf = theTerminal;
  tiosbuf.c_iflag |= IGNBRK;
  tiosbuf.c_iflag |= IGNPAR;
  tiosbuf.c_lflag &= ~ICANON;
  tiosbuf.c_lflag &= ~ECHO;
  tiosbuf.c_lflag &= ~ISIG;
  tiosbuf.c_cc[ VMIN ] = 1;
  tiosbuf.c_cc[ VTIME ] = 0;
  tcsetattr( 0, TCSANOW, &tiosbuf );

  return;
}

Tvoid TTerminalUserInterface::resetTerminal()
{
  tcsetattr( 0, TCSANOW, &theTerminal );
  return;
}

Tbool TTerminalUserInterface::AcceptCommand()
{
  return ( theAcceptString.c_str() != 0 ) ? Ttrue : Tfalse;
}

const Tstring& TTerminalUserInterface::GetInputCommand()
{
  return readLine();
}

Tvoid TTerminalUserInterface::NotFoundCommand( const Tstring& commandname ) const
{
  std::cerr << commandname << ": Command not found." << std::endl;
  return;
}

TstringList TTerminalUserInterface::divide( const Tstring& input ) const
{
  TstringList divided;
  Tsize_t begin = 0;
  Tsize_t end = 0;
  Tsize_t inputlen = input.size();

  do {
    begin = input.find_first_not_of( ' ', end );
    end = input.find( ' ', begin );
    if ( begin < inputlen && end >= inputlen ) {
      divided.push_back( input.substr( begin, inputlen - begin ) );
    } else if ( begin >= inputlen && end >= inputlen ) {
      break;
    } else {
      divided.push_back( input.substr( begin, end - begin ) );
    }
  } while ( begin < inputlen && end < inputlen );

  return divided;
}

#ifdef __CLDAQ_ROOT_DLL
    ClassImp(TTerminalUserInterface)
#endif
