/*
 * Copyright (c) 2002-2006 Samit Basu
 *
 * 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 of the License, 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
 *
 */
#include "QTTerm.hpp"
#include "KeyManager.hpp"
#include <qapplication.h>
#include <qclipboard.h>
#include <math.h>
#include <QtGui>
#include <QEvent>

QTTerm::QTTerm() {
  setMinimumSize(50,50);
  setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  buffer << tagLine();
  cursor_x = 0;
  cursor_y = 0;
  blinkEnable = true;
  m_blink_skip = false;
  m_timer_blink = new QTimer;
  QObject::connect(m_timer_blink, SIGNAL(timeout()), this, SLOT(blink()));
  QSettings settings("FreeMat", "FreeMat");
  scrollback = settings.value("console/scrollback",5000).toInt();
  m_timer_blink->start(settings.value("console/blinkspeed",1000).toInt());
  fnt = QFont("Courier",10);
}

int QTTerm::getScrollbackLimit() {
  return scrollback;
}

void QTTerm::setScrollbackLimit(int m) {
  scrollback = m;
  while (buffer.size() > scrollback) {
    buffer.pop_front();
    cursor_y = qMax(0,cursor_y-1);
  }
  setCursor();
  updateScrollbarSettings();
}

void QTTerm::blink() {
  if (!blinkEnable) return;
  if (m_blink_skip) {
    m_blink_skip = false;
    return;
  }
  buffer[cursor_y].data[cursor_x].toggleCursor();
  viewport()->update();
}

void QTTerm::ensureCursorVisible() {
  // For the cursor to be visible
  // the scroll bar must be at 
  // cursor_y - m_term_height + 1
  int cscroll = verticalScrollBar()->value();
  if ((cscroll < cursor_y) && 
      (cursor_y < (cscroll+m_term_height-1))) return;
  int minval = cursor_y-m_term_height+1;
  verticalScrollBar()->setValue(minval);
}

void QTTerm::focusOutEvent(QFocusEvent *e) {
  QWidget::focusOutEvent(e);
  blinkEnable = false;
  buffer[cursor_y].data[cursor_x].clearCursor();  
  viewport()->update();
}

void QTTerm::focusInEvent(QFocusEvent *e) {
  QWidget::focusInEvent(e);
  buffer[cursor_y].data[cursor_x].setCursor();
  viewport()->update();
  blinkEnable = true;
}

void QTTerm::setChar(char t, bool flush) {
  if (t == '\r') {
    MoveBOL();
    return;
  }
  if (t == '\n') {
    nextLine();
    return;
  }
  blinkEnable = false;
  buffer[cursor_y].data[cursor_x].clearCursor();
  buffer[cursor_y].data[cursor_x++].v = t;
  buffer[cursor_y].data[cursor_x].setCursor();
  if (cursor_x >= m_term_width) {
    nextLine(); 
  } else {
    if (flush) {
      ensureCursorVisible();
      viewport()->update(QRect(((cursor_x)-1)*m_char_w,
			       (cursor_y-verticalScrollBar()->value())*m_char_h,
			       m_char_w*2,m_char_h));
    }
  }
  blinkEnable = true;
  m_blink_skip = true;
}

void QTTerm::setFont(QFont font) {
  QFontInfo fi(font);
  if (!fi.fixedPitch()) 
    QMessageBox::warning(this,"FreeMat",
			 "You have selected a font that is not a fixed pitch.\nThe terminal widget really requires a fixed pitch font to work.");
  fnt = font;
  calcGeometry();
}

QFont QTTerm::getFont() {
  return fnt;
}

void QTTerm::clearCursor() {
  blinkEnable = false;
  buffer[cursor_y].data[cursor_x].clearCursor();
}

void QTTerm::setCursor() {
  buffer[cursor_y].data[cursor_x].setCursor();
  ensureCursorVisible();
  viewport()->update();
  blinkEnable = true;
  m_blink_skip = true;    
}

void QTTerm::MoveDown() {
  clearCursor();
  cursor_y++;
  if (cursor_y >= buffer.size())
    buffer << tagLine();
  if (buffer.size() > scrollback) {
    buffer.pop_front();
    cursor_y--;
  } else {
    int cval = verticalScrollBar()->value();
    verticalScrollBar()->setRange(0,qMax(0,qMax(verticalScrollBar()->maximum(),buffer.size()-m_term_height)));
    verticalScrollBar()->setValue(cval);
  }
  setCursor();
}

void QTTerm::MoveBOL() {
  clearCursor();
  cursor_x = 0;
  setCursor();
}

void QTTerm::MoveUp() {
  clearCursor();
  cursor_y = qMax(0,cursor_y-1);
  setCursor();  
}

void QTTerm::MoveLeft() {
  clearCursor();
  cursor_x = qMax(0,cursor_x-1);
  setCursor();
}

void QTTerm::MoveRight() {
  clearCursor();
  cursor_x = qMin(m_term_width-1,cursor_x+1);
  setCursor();
}

void QTTerm::ClearEOL() {
  for (int j=cursor_x;j<m_term_width-1;j++) {
    buffer[cursor_y].data[j].v = ' ';
    buffer[cursor_y].data[j].flags = 0;
  }
  viewport()->update();
}

void QTTerm::ClearEOD() {
  ClearEOL();
  for (int i=cursor_y+1;i<buffer.size();i++) {
    for (int j=0;j<m_term_width-1;j++) {
      buffer[i].data[j].v = ' ';
      buffer[i].data[j].flags = 0;
    }
  }
  viewport()->update();
}

void QTTerm::ClearDisplay() {
  blinkEnable = false;
  buffer.clear();
  buffer << tagLine();
  cursor_y = 0;
  cursor_x = 0;
  verticalScrollBar()->setRange(0,buffer.size()-m_term_height);
  verticalScrollBar()->setValue(0);
  viewport()->update();
  setCursor();
}

void QTTerm::nextLine() {
  MoveBOL();
  MoveDown();
  cursor_x = 0;
  viewport()->update();
}

void QTTerm::drawLine(int linenum, QPainter *e, int yval) {
  QString outd;
  tagLine todraw(buffer[linenum]);
  char gflags = 0;
  int frag_start = 0;
  for (int col=0;col<m_term_width;col++) {
    tagChar g(todraw.data[col]);
    if (g.mflags() != gflags) {
      drawFragment(e,outd,gflags,yval,frag_start);
      gflags = g.mflags();
      frag_start = col;
      outd.clear();
      outd.append(g.v);
    } else
      outd.append(g.v);
  }
  drawFragment(e,outd,gflags,yval,frag_start);
}

void QTTerm::mousePressEvent( QMouseEvent *e ) {
  // Get the x and y coordinates of the mouse click - map that
  // to a row and column
  int clickcol = e->x()/m_char_w;
  int clickrow = e->y()/m_char_h;
  selectionStart = verticalScrollBar()->value()*m_term_width + clickcol + clickrow*m_term_width;
  selectionStart = qMax(0,selectionStart);
  selectionStop = selectionStart;
}

void QTTerm::clearSelection() {
  // clear the selection bits
  for (int i=0;i<buffer.size();i++) {
    for (int j=0;j<maxlen;j++) {
      buffer[i].data[j].clearSelection();
    }
  }
}

void QTTerm::mouseMoveEvent( QMouseEvent *e ) {
  if (!e->buttons())
    return;
  int x = e->x();
  int y = e->y();
  if (y < 0) 
     verticalScrollBar()->setValue(verticalScrollBar()->value()-1);
   if (y > height())
     verticalScrollBar()->setValue(verticalScrollBar()->value()+1);
  // Get the position of the click
  // to a row and column
  int clickcol = x/m_char_w;
  int clickrow = y/m_char_h;
  selectionStop = verticalScrollBar()->value()*m_term_width + clickcol + clickrow*m_term_width;
  selectionStop = qMax(0,selectionStop);

  clearSelection();

  int lSelectionStart = selectionStart;
  int lSelectionStop = selectionStop;
  if (lSelectionStart > lSelectionStop) 
    qSwap(lSelectionStop,lSelectionStart);

  int sel_row_start = lSelectionStart/m_term_width;
  int sel_col_start = lSelectionStart % m_term_width;
  int sel_row_stop = lSelectionStop/m_term_width;
  int sel_col_stop = lSelectionStop % m_term_width;

  sel_row_start = qMin(sel_row_start,buffer.size()-1);
  sel_row_stop = qMin(sel_row_stop,buffer.size()-1);

  if (sel_row_stop == sel_row_start) {
    for (int j=sel_col_start;j<sel_col_stop;j++)
      buffer[sel_row_start].data[j].setSelection();
  } else {
    for (int j=sel_col_start;j<m_term_width;j++) {
      buffer[sel_row_start].data[j].setSelection();
    }
    for (int i=sel_row_start+1;i<sel_row_stop;i++) 
      for (int j=0;j<m_term_width;j++) {
	buffer[i].data[j].setSelection();
      }
    for (int j=0;j<sel_col_stop;j++)
      buffer[sel_row_stop].data[j].setSelection();
  }
  viewport()->update();
}

void QTTerm::mouseReleaseEvent( QMouseEvent *e ) {
  QClipboard *cb = QApplication::clipboard();
  if (!cb->supportsSelection())
    return;
  cb->setText(getSelectionText(), QClipboard::Selection);
}

void QTTerm::drawFragment(QPainter *paint, QString todraw, char flags, int row, int col) {
  if (todraw.size() == 0) return;
  QRect txtrect(col*m_char_w,row*m_char_h,(col+todraw.size())*m_char_w,m_char_h);
  QPalette qp(qApp->palette());
  if (flags == 0) {
    paint->setPen(qp.color(QPalette::WindowText));
    paint->setBackground(qp.brush(QPalette::Base));
    paint->eraseRect(txtrect);
    paint->drawText(txtrect,Qt::AlignLeft|Qt::AlignTop,todraw);
  } else if (flags & CURSORBIT) {
    paint->setPen(qp.color(QPalette::Base));
    paint->setBackground(Qt::black);
    paint->eraseRect(txtrect);
    paint->drawText(txtrect,Qt::AlignLeft|Qt::AlignTop,todraw);
  } else {
    paint->setPen(qp.color(QPalette::HighlightedText));
    paint->setBackground(qp.brush(QPalette::Highlight));
    paint->eraseRect(txtrect);
    paint->drawText(txtrect,Qt::AlignLeft|Qt::AlignTop,todraw);
  }
}

#ifndef __APPLE__
#define CTRLKEY(x)  else if ((keycode == x) && (e->modifiers() & Qt::ControlModifier))
#else
#define CTRLKEY(x)  else if ((keycode == x) && (e->modifiers() & Qt::MetaModifier))
#endif


bool QTTerm::event(QEvent *e) {
  if (e->type() == QEvent::KeyPress) {
    QKeyEvent *ke = static_cast<QKeyEvent*>(e);
    if (ke->key() == Qt::Key_Tab) {
      emit OnChar(KM_TAB);
      return true;
    }
  }
  return QAbstractScrollArea::event(e);
}

void QTTerm::keyPressEvent(QKeyEvent *e) {
  int keycode = e->key(); 
  if (!keycode) return;
  if (keycode == Qt::Key_Left)
    emit OnChar(KM_LEFT);
  CTRLKEY('Z')
    emit OnChar(KM_CTRLK);
  CTRLKEY('A')
    emit OnChar(KM_CTRLA);
  CTRLKEY('D')
    emit OnChar(KM_CTRLD); 
  CTRLKEY('E')
    emit OnChar(KM_CTRLE);
  CTRLKEY('K')
    emit OnChar(KM_CTRLK);
  CTRLKEY('Y')
    emit OnChar(KM_CTRLY);
  else if (keycode == Qt::Key_Right)
    emit OnChar(KM_RIGHT);
  else if (keycode == Qt::Key_Up)
    emit OnChar(KM_UP);
  else if (keycode == Qt::Key_Down)
    emit OnChar(KM_DOWN);
  else if (keycode == Qt::Key_Delete)
    emit OnChar(KM_DELETE);
  else if (keycode == Qt::Key_Insert)
    emit OnChar(KM_INSERT);
  else if (keycode == Qt::Key_Home)
    emit OnChar(KM_HOME);
  else if (keycode == Qt::Key_End)
    emit OnChar(KM_END);
  else if (keycode == Qt::Key_Return)
    emit OnChar(KM_NEWLINE);
  else if (keycode == Qt::Key_Backspace)
    emit OnChar(KM_BACKSPACE);
  else {
    QByteArray p(e->text().toAscii());
    char key;
    if (!e->text().isEmpty())
      key = p[0];
    else
      key = 0;
    if (key) {
      emit OnChar(key);
      e->accept();
    } else
      e->ignore();
  }
}

void QTTerm::OutputRawString(string txt) {
  for (int i=0;i<txt.size();i++)
    setChar(txt[i],true);
}

void QTTerm::calcGeometry() {
  QFontMetrics fmi(fnt);
  m_char_w = fmi.width("w");
  m_char_h = fmi.height();
  m_term_width = viewport()->width()/m_char_w - 1;
  m_term_height = viewport()->height()/m_char_h;
  emit SetTextWidth(m_term_width);
}

void QTTerm::resizeEvent(QResizeEvent *e) {
  QAbstractScrollArea::resizeEvent(e);
  calcGeometry();
  clearSelection();
  ensureCursorVisible();
  updateScrollbarSettings();
}

void QTTerm::updateScrollbarSettings() {
  // If we are in a full buffer situation, put the scroller in the right spot
  if (buffer.size() >= scrollback) {
    verticalScrollBar()->setRange(0,buffer.size()-m_term_height);
    verticalScrollBar()->setValue(cursor_y-m_term_height+1);
  } else {
    int cval = verticalScrollBar()->value();
    verticalScrollBar()->setRange(0,qMax(cval,buffer.size()-m_term_height));
    verticalScrollBar()->setValue(cval);
  }
}

void QTTerm::paintEvent(QPaintEvent *e) {
  QPainter p(viewport());
  p.setFont(fnt);
  //  qDebug() << "Current font: " << p.font().toString();
//   p.setBackground(qApp->palette().brush(QPalette::Highlight));
//   p.setPen(qApp->palette().color(QPalette::HighlightedText));
  int offset = verticalScrollBar()->value();
  //  qDebug() << "offset = " << offset;
  for (int i=0;i<m_term_height;i++) 
    if ((i+offset) < buffer.size())
      drawLine(i+offset,&p,i);
}

QString QTTerm::getAllText() {
  QString ret;
  for (int i=0;i<buffer.size();i++) {
    for (int j=0;j<maxlen;j++)
      ret += buffer[i].data[j].v;
    ret += '\n';
  }
  ret.replace(QRegExp(" +\\n"),"\n");
  return ret;
}

QString QTTerm::getSelectionText() {
  QString ret;
  for (int i=0;i<buffer.size();i++) {
    bool lineHasSelectedText = false;
    for (int j=0;j<maxlen;j++)
      if (buffer[i].data[j].selected()) {
	ret += buffer[i].data[j].v;
	lineHasSelectedText = true;
      }
    if (lineHasSelectedText)
      ret += '\n';
  }
  ret.replace(QRegExp(" +\\n"),"\n");
  return ret;
}



syntax highlighted by Code2HTML, v. 0.9.1