/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "ifaceterm.hh"

namespace ghidra {

IfaceTerm::IfaceTerm(const string &prmpt,istream &is,ostream &os)
  : IfaceStatus(prmpt,os)
{
  sptr = &is;
#ifdef __TERMINAL__
  struct termios ittypass;

  is_terminal = true;
  //  ifd = fileno( (FILE *)sptr->rdbuf() );
  ifd = 0;			// The above line doesn't work on some systems
				// and since ifd will almost always refer to stdin...
  
  if (0>tcgetattr(ifd,&itty)) { // Get original terminal settings 
    if (errno == EBADF)
      throw IfaceError("Bad input file descriptor passed to iface");
    else if (errno == ENOTTY) {
      is_terminal = false;
      return;
    }
    throw IfaceError("Unknown error with input file stream");
  }
                              // Build terminal settings for entering passphrase 
  ittypass = itty;
  ittypass.c_lflag &= ~((tcflag_t)ECHO); // Turn off echo 
  ittypass.c_lflag &= ~((tcflag_t)ICANON); // Turn of buffered input 
  ittypass.c_cc[VMIN] = 1;    // Buffer only one character at a time 
  ittypass.c_cc[VTIME] = 0;   // Do not time out 

  if (0>tcsetattr(ifd,TCSANOW,&ittypass))
    throw IfaceError("Unable to set terminal attributes");
#endif
}

IfaceTerm::~IfaceTerm(void)

{
  while(!inputstack.empty()) {
    delete sptr;
    sptr = inputstack.back();
    inputstack.pop_back();
  }
#ifdef __TERMINAL__
  if (is_terminal) {
    tcsetattr(ifd,TCSANOW,&itty); // Restore original terminal settings
  }
#endif
}

/// Respond to a TAB key press and try to 'complete' any existing tokens.
/// The method is handed the current state of the command-line in a string, and
/// it updates the command-line in place.
///
/// \param line is current command-line and will hold the final completion
/// \param cursor is the current position of the cursor
/// \return the (possibly new) position of the cursor, after completion
int4 IfaceTerm::doCompletion(string &line,int4 cursor)

{
  vector<string> fullcommand;
  istringstream s(line);
  string tok;
  vector<IfaceCommand *>::const_iterator first,last;
  int4 oldsize,match;

  first = comlist.begin();
  last = comlist.end();
  match = expandCom(fullcommand,s,first,last); // Try to expand the command
  if (match == 0) {
    *optr << endl << "Invalid command" << endl;
    return cursor;		// No change to command line
  }

				// At least one match
  oldsize = line.size();
  wordsToString(line,fullcommand);
  if (match<0)
    match = -match;
  else
    line += ' ';		// Provide extra space if command word is complete
  if (!s.eof()) {		// Read any additional parameters back to command line
    s >> tok >> ws;
    line += tok;		// Assume first space is present before extra params
  }
  while(!s.eof()) {
    line += ' ';		// Provide space between extra parameters
    s >> tok >> ws;
    line += tok;
  }
  if (oldsize < line.size())	// If we have expanded at all
    return line.size();		// Just display expansion
  
  if (match > 1) {		// If more than one possible command
    string complete;		// Display all possible completions
    *optr << endl;
    for(;first!=last;++first) {
      (*first)->commandString(complete); // Get possible completion
      *optr << complete << endl;
    }
  }
  else				// Command is unique and expanded
    *optr << endl << "Command is complete" << endl;
  return line.size();
}
  
void IfaceTerm::readLine(string &line)

{
  char val;
  int4 escval;
  int4 cursor,lastlen,i;
  bool onecharecho;
  int4 hist;
  string saveline;

  line.erase();
  cursor = 0;
  hist = 0;
  do {
    onecharecho = false;
    lastlen = line.size();
    val = sptr->get();
    if (sptr->eof())
      val = '\n';
    switch(val) {
    case 0x01:			// C-a
      cursor = 0;		// Jump to beginning
      break;
    case 0x02:			// C-b
      if (cursor>0)
	cursor -= 1;		// back up one char
      break;
    case 0x03:			// C-c
      line.erase();		// As if we cleared the line and hit return
      cursor = 0;
      val = 0x0a;
      onecharecho = true;
      break;
    case 0x04:			// C-d
      line.erase(cursor,1);	// Delete character cursor is on
      break;
    case 0x05:			// C-e
      cursor = line.size();	// Jump to end
      break;
    case 0x06:			// C-f
      if (cursor < line.size())
	cursor += 1;		// Move forward one character
      break;
    case 0x07:			// C-g
      break;			// do nothing
    case 0x09:			// TAB
      cursor = doCompletion(line,cursor);
      break;
    case 0x0a:			// Newline
    case 0x0d:			// Carriage return
      cursor = line.size();
      onecharecho = true;
      break;
    case 0x0b:			// C-k
      line.erase(cursor);
      break;
    case 0x0c:			// C-l
      break;
    case 0x0e:			// C-n
      if (hist >0) {
	hist -= 1;		// Get more recent history
	if (hist>0)
	  getHistory(line,hist-1);
	else
	  line = saveline;
	cursor = line.size();
      }
      break;
    case 0x10:			// C-p
      if (hist < getHistorySize()) { 
	hist += 1;		// Get more ancient history
	if (hist==1)
	  saveline = line;
	getHistory(line,hist-1);
	cursor = line.size();
      }
      break;
    case 0x12:			// C-r
      break;
    case 0x15:			// C-u
      line.erase(0,cursor);	// Erase up to cursor
      cursor = 0;
      break;
    case 0x1b:			// Escape character
      escval = sptr->get();
      escval <<= 8;
      escval += sptr->get();
      switch(escval) {
      case 0x4f44:		// left arrow
	if (cursor>0)
	  cursor -= 1;
	break;
      case 0x4f43:		// right arrow
	if (cursor<line.size())
	  cursor += 1;
	break;
      }
      break;
    case 0x08:
    case 0x7f:			// delete
      if (cursor!=0)
	line.erase(--cursor,1);
      break;
    default:
      line.insert(cursor++,1,val); // Insert single character
      if (cursor == line.size())
	onecharecho = true;
      break;
    }
    if (onecharecho)
      optr->put(val);		// Echo most characters
    else {
      optr->put('\r');		// Ontop of old line
      writePrompt();
      *optr << line;		// print new line
      for(i=line.size();i<lastlen;++i)
	optr->put(' ');		// Erase any old characters
      for(i=i-cursor;i>0;--i)
	optr->put('\b');	// Put cursor in the right place
    }
  } while(val != '\n');
}

void IfaceTerm::pushScript(istream *iptr,const string &newprompt)

{
  inputstack.push_back(sptr);
  sptr = iptr;
  IfaceStatus::pushScript(iptr,newprompt);
}

void IfaceTerm::popScript(void)

{
  delete sptr;
  sptr = inputstack.back();
  inputstack.pop_back();
  IfaceStatus::popScript();
}

bool IfaceTerm::isStreamFinished(void) const

{
  if (done||inerror) return true;
  return sptr->eof();
}

} // End namespace ghidra
