/***************************************************************************
                          kashview.cpp  -  description
                             -------------------
    begin                : Son Nov 21 15:23:42 CET 1999
    copyright            : (C) 1999 by Stephan Kahnt
    email                : stephan.kahnt@ipk.fhg.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

// standard include
#include <iostream>
#include <cfloat>
#include <strstream>

// include files for Qt
#include <qprinter.h>
#include <qpainter.h>
#include <qdatetime.h> 
#include <qtextstream.h>
#include <qheader.h>
#include <qaccel.h> 

// include files for KDE
#include <klocale.h>
#include <kmessagebox.h>
#include <kglobal.h>
#include <kconfig.h>
#include <ksimpleconfig.h>
#include <kstddirs.h>

// application specific includes
#include "kashview.h"
#include "kashdoc.h"
#include "kash.h"
#include "kashaccount.h"
#include "resource.h"

// the header label
const char* const KashView::headerLabel[] = { "Date", "Nr", "Payee", "Category",
					      "Subcategory", "Memo", "State", "Amount",
					      "Subtotal" };

KashView::KashView(QWidget* parent, const char* name) : QWidget(parent, name) {
  //  setBackgroundMode( PaletteBase );
  
  // Make the top-level layout; a vertical box to contain all widgets
  // and sub-layouts.

  QBoxLayout *topLayout = new QVBoxLayout( this, 10 );
  CHECK_PTR( topLayout );

  transTab = new QListView( this );
  CHECK_PTR( transTab );
  topLayout->addWidget( transTab );
  transTab->setSelectionMode( QListView::Single );
  transTab->setShowSortIndicator( true );
  connect( transTab->header(),  SIGNAL( sectionClicked( int ) ),
	   SLOT( slotHeaderClicked( int ) ) );
  connect( transTab, SIGNAL( clicked( QListViewItem* ) ),
	   SLOT( slotTransTabClicked( QListViewItem* ) ) );

  for ( int i = 0; i < FIELD_NO; i++ ) {
    transTab->addColumn( i18n( headerLabel[ i ] ) );
  }
  
  transTab->setColumnAlignment( AMOUNT_IDX, AlignRight );
  transTab->setColumnAlignment( SUM_IDX, AlignRight );
  
  QPushButton *newButton = new QPushButton( i18n("&New"), this );
  connect( newButton, SIGNAL( clicked() ), SLOT( slotNewButton() ) );

  QPushButton *updateButton = new QPushButton( i18n("&Update"), this );
  connect( updateButton, SIGNAL( clicked() ), SLOT( slotUpdateButton() ) );

  QPushButton *deleteButton = new QPushButton( i18n( "&Delete" ), this );
  connect( deleteButton, SIGNAL( clicked() ), SLOT( slotDelButton() ) );

  balanceLabel = new QLabel( this );
  QLabel* balanceLabelName = new QLabel( i18n( "Balance :" ), this );

  QBoxLayout* buttonLayout = new QHBoxLayout( topLayout );
  buttonLayout->addWidget( newButton );
  buttonLayout->addWidget( updateButton );
  buttonLayout->addWidget( deleteButton );
  buttonLayout->addStretch( 10 );
  buttonLayout->addWidget( balanceLabelName );
  buttonLayout->addWidget( balanceLabel );

  payeeLE = new QLineEdit( this, "payeeLE" );
  QLabel *payeeLabel = new QLabel( payeeLE, i18n( "&Payee" ), this );

  catLE = new QLineEdit( this, "catLE" );
  QLabel *catLabel = new QLabel( catLE, i18n("&Category"), this );

  subCatLE = new QLineEdit( this, "subCatLE" );

  memoLE = new QLineEdit( this, "memoLE" );
  QLabel *memoLabel = new QLabel( memoLE, i18n("&Memo"), this );

  QGridLayout* grid = new QGridLayout( topLayout, 5, 3, 10 );

  grid->addWidget( payeeLabel, 0, 0 );
  grid->addWidget( payeeLE, 0, 1 );
  grid->addWidget( catLabel, 1, 0 );
  grid->addWidget( catLE, 1, 1 );
  grid->addWidget( subCatLE, 1, 2 );
  grid->addWidget( memoLabel, 2, 0 );
  grid->addWidget( memoLE, 2, 1 );
  grid->setColStretch( 1, 10 );
  grid->setColStretch( 2, 10 );

  nrLE = new QLineEdit( this );
  QLabel* nrLabel = new QLabel( nrLE, i18n("N&o."), this );

  dateLine = new KashDateLineEdit( this );
  QLabel* dateLabel = new QLabel( dateLine, i18n("Da&te"), this );

  amountLine = new QLineEdit( this );
  amountLine->setValidator( new KashAmountValidator( -DBL_MAX, DBL_MAX, 
						     2, amountLine ) );
  QLabel* amountLabel = new QLabel( amountLine, i18n( "&Amount" ), this );
  connect( amountLine, SIGNAL( textChanged( const QString & ) ),
	   SLOT( amountLineChanged( const QString & ) ) );
  

  grid->addWidget( nrLabel, 0, 3 );
  grid->addWidget( nrLE, 0, 4 );
  grid->addWidget( dateLabel, 1, 3 );
  grid->addWidget( dateLine, 1, 4 );
  grid->addWidget( amountLabel, 2, 3 ); 
  grid->addWidget( amountLine, 2, 4 );

  sum = 0;
}

KashView::~KashView() {
}

KashDoc *KashView::getDocument() const {
  KashApp *theApp=( KashApp* )parentWidget();
  
  return theApp->getDocument();
}

void KashView::print( QPrinter* pPrinter ) {
  QPainter printpainter;
  printpainter.begin( pPrinter );
	
  // TODO: add your printing code here

  printpainter.end();
}

/** callback for the new button */
void KashView::slotNewButton() {
  StringVector dataVec( FIELD_NO, QString( "" ) );
  long newId = 0;

  dataVec[ DATE_IDX ] = KGlobal::locale()->formatDate( QDate::currentDate(), true );
  dataVec[ AMOUNT_IDX ] = "0.00";

  // insert a new translation in the document ( the current account )
  if ( ! ( newId = getDocument()->addTransaction( dataVec ) ) ) {
    return;
  }

  // insert a new translation in the list
  KashListViewItem* transLine = new KashListViewItem( transTab, newId );
  CHECK_PTR( transLine );
  idDict.insert( newId, transLine );

  transTab->setSelected( transLine, true );

  // use the content of the input mask
  slotUpdateButton();
}

/** callback for the delete button */
void KashView::slotDelButton() {
  KashListViewItem* transLine = ( KashListViewItem* )transTab->selectedItem();
  if ( !transLine ) {
    return;
  }

  idDict.remove( transLine->transId() );  
  delete transLine;
  
  getDocument()->deleteTransaction( transLine->transId() );
}

/** adds or updates a transaction in the list view */  
void KashView::slotListTransaction( long transId, StringVector& dataVec ) {
  KashListViewItem* actItem = 0L;
  if ( idDict.find( transId ) ) {
    actItem = idDict[ transId ];
  }
  else {
    actItem = new KashListViewItem( transTab, transId );
    idDict.insert( transId, actItem );
  }

  // update the input mask
  actItem->setText( DATE_IDX, dataVec[ DATE_IDX ] );
  actItem->setText( NO_IDX, dataVec[ NO_IDX ] );
  actItem->setText( PAYEE_IDX, dataVec[ PAYEE_IDX ] );
  actItem->setText( CAT_IDX, dataVec[ CAT_IDX ] );
  actItem->setText( SUBCAT_IDX, dataVec[ SUBCAT_IDX ] );
  actItem->setText( MEMO_IDX, dataVec[ MEMO_IDX ] );
  actItem->setText( AMOUNT_IDX, 
		    KGlobal::locale()->formatNumber( dataVec[  AMOUNT_IDX ].toDouble(), 
						     2 ) );

  // update the sum row
  slotHeaderClicked();

  balanceLabel->setText( KGlobal::locale()->formatMoney( sum ) );
}

/** removes all transactions from the list */
void KashView::slotNewModel() {
  idDict.clear();
  transTab->clear();
}

/** even refuse input that is QValidator::Intermediate */
void KashView::amountLineChanged( const QString & text ) {
  QString dummy( text );
  int curPos = amountLine->cursorPosition(); 
  if ( amountLine->validator()->validate( dummy, curPos ) ==
       QValidator::Intermediate ) {
    amountLine->backspace();
  }
}

/** callback for the update button */
void KashView::slotUpdateButton() {
  KashListViewItem* transLine = ( KashListViewItem* )transTab->selectedItem();
  if ( !transLine ) {
    return;
  }
  
  QString dateStr = dateLine->text();
  int datePos = dateLine->cursorPosition();
  if ( dateLine->validator()->validate( dateStr, datePos ) !=
       QValidator::Acceptable ) {
    KMessageBox::error( this, "The date is not valid!\n"
			"Please correct it.\n"
			"Use Ctrl-T to insert the actual date",
			"Date unvalid!" );
    return;
  }

  // fill the data vector to update the model
  StringVector dataVec( FIELD_NO );
  dataVec[ DATE_IDX ] = dateLine->text();
  QString strAmount = amountLine->text();
  amountLine->validator()->fixup( strAmount );
  amountLine->setText( strAmount );
  double amount = amountLine->text().toDouble();
  dataVec[ AMOUNT_IDX ] = KGlobal::locale()->formatNumber( amount, 2 );
  dataVec[ PAYEE_IDX ] = payeeLE->text();
  dataVec[ CAT_IDX ] = catLE->text();
  dataVec[ SUBCAT_IDX ] = subCatLE->text();
  dataVec[ MEMO_IDX ] = memoLE->text();
  dataVec[ NO_IDX ] = nrLE->text();
  dataVec[ STATE_IDX ] = "";
  if ( ! getDocument()->updateTransaction( transLine->transId(), dataVec ) ) {
    return;
  }

  // update the transaction list

  transLine->setText( DATE_IDX, dateLine->text() );
  transLine->setText( NO_IDX, nrLE->text() );
  transLine->setText( PAYEE_IDX, payeeLE->text() );
  transLine->setText( CAT_IDX, catLE->text() );
  transLine->setText( SUBCAT_IDX, subCatLE->text() );
  transLine->setText( MEMO_IDX, memoLE->text() );
  transLine->setText( AMOUNT_IDX, amountLine->text() );

  // update the sum row
  slotHeaderClicked();

  balanceLabel->setText( KGlobal::locale()->formatMoney( sum ) );
}

/** callback for a click in the transaction table */
void KashView::slotTransTabClicked( QListViewItem* item ) {
  if ( !item ) {
    return;
  }

  // update the input mask
  dateLine->setText( item->text( DATE_IDX ) );
  nrLE->setText( item->text( NO_IDX ) );
  payeeLE->setText( item->text( PAYEE_IDX ) );
  catLE->setText( item->text( CAT_IDX ) );
  subCatLE->setText( item->text( SUBCAT_IDX ) );
  memoLE->setText( item->text( MEMO_IDX ) );
  amountLine->setText( item->text( AMOUNT_IDX ) );
}

/** callback for a click on the header */
void KashView::slotHeaderClicked( int ) {
  // recalculate the sum row 
  sum = 0;
  QListViewItemIterator it( transTab );  
  KashListViewItem* item = 0L;
  for ( ; it.current(); ++it ) {
    item = ( KashListViewItem* )it.current();
    sum += item->amount();
    item->setText( SUM_IDX, KGlobal::locale()->formatNumber( sum, 2 ) );
  } 
}

/** adds missing "0"'s behind the point */
void KashAmountValidator::fixup( QString& input ) const {
  cout << "void KashAmountValidator::fixup( " << input << " ) const " << endl;
  input = KGlobal::locale()->formatNumber( input.toDouble(), 2 );
}

/** helper function that return the short date format of the current config */
static QString getDateFormat() {
  QString dateFmt;
  // read the date format 
  KConfig* config = KGlobal::config();
  QString lang = config->readEntry( "Time", KGlobal::locale()->language() );
  if ( lang.isNull() ) {
    lang = "default";
  }
  
  KSimpleConfig timentry( locate( "locale", "l10n/" + lang + "/entry.desktop"), true);
    
  dateFmt = config->readEntry( "DateFormatShort" );
  if ( dateFmt.isNull() ) {
    dateFmt = timentry.readEntry( "DateFormatShort", "%m/%d/%y" );
  }
  return dateFmt;
}

/** helper function that converts a string that fits to the current date format
    in a QDate */
QDate stringToDate( const QString& dateString ) {
  static QString dateFmt =  getDateFormat();
  
  istrstream dateStream( dateString.latin1() );

  int y, m, d;

  int i = 0;
  int prev = -1;
  while ( ( i = dateFmt.find( '%', i ) ) != -1 ) {
    if ( prev != -1 && prev != i - 2 ) {
      dateStream.seekg( i - prev - 2, ios::cur );
    }
    switch ( dateFmt.at( i + 1 ).latin1() ) {
    case 'Y':
    case 'y':
      dateStream >> y;
      break;
    case 'n':
    case 'm':
      dateStream >> m;
      break;
    case 'd':
    case 'e':
      dateStream >> d;
      break;
    }
    prev = i++;
  }

  y < 45 ? y += 2000 : y += 1900;

  return QDate( y, m, d );
}

/** checks the input of the date line edit */
KashDateValidator::State 
KashDateValidator::validate( QString& text, int & ) const {
  if ( text.isEmpty() ) {
    return Intermediate;
  }

  static QString dateFmt = getDateFormat();

  istrstream dateStream( text.latin1() );

  uint m = 1;
  uint d = 1;
  int y = 2000;
  uint complete = 0;

  int i = 0;
  int prev = -1;
  // until the input or the format is out
  while ( dateStream.peek() != - 1 && ( i = dateFmt.find( '%', i ) ) != -1 ) {
    // check the format between % tokens
    if ( prev != -1 ) {
      for ( int j = prev + 2; j < i && dateStream.peek() != -1; j++ ) {
	if ( dateStream.get() != dateFmt.at( j++ ) ) {
	  return Invalid;
	}
      }
    }

    if ( dateStream.peek() == -1 ) {
      break;
    }

    switch ( dateFmt.at( i + 1 ).latin1() ) {
    case 'Y':
    case 'y':
      if ( !( dateStream >> y ) ) {
	return Invalid;
      }
      complete = complete | 1;
      break;
    case 'n':
    case 'm':
      if ( !( dateStream >> m ) ) {
	return Invalid;
      }
      complete = complete | 2;
      break;
    case 'd':
    case 'e':
      if ( !( dateStream >> d ) ) {
	return Invalid;
      }
      complete = complete | 4;
      break;
    }
    prev = i++;
  }

  // check the rest after the last % token
  prev += 2;
  while ( dateStream.peek() != -1 ) {
    if ( dateStream.get() != dateFmt.at( prev ) ) {
      return Invalid;
    }
  }
  // correct the years for the current century
  if ( y < 45 ) {
    y += 2000;
  } else if ( y < 100 ) {
    y += 1900;
  }
  // check the usual limits
  if ( m == 0 || m > 12 || 
       d == 0 || d > 31 || 
       y < 0 || y > 8000 ) {
    return Invalid;
  }
  
  if ( complete == 7 && QDate::isValid( y, m, d ) ) {
    return Acceptable;
  }
  else {
    return Intermediate;
  }
}

/** a QLineEdit class for the date input */
KashDateLineEdit::KashDateLineEdit( QWidget* parent ) : QLineEdit( parent ) {
  setValidator( new KashDateValidator( this ) );
}

/** handel the special keys for the date input */
void KashDateLineEdit::keyPressEvent( QKeyEvent* ev ) {
  QString valText = text();
  int valPos = cursorPosition();
  switch ( ev->key() ) {
  case Key_Plus:
    if ( validator()->validate( valText, valPos ) 
	 == QValidator::Acceptable ) {
      setText( KGlobal::locale()->formatDate( stringToDate( text() ).addDays( 1 ), true ) );
    }
    ev->accept();
    break;
  case Key_Minus:
    if ( validator()->validate( valText, valPos ) 
	 == QValidator::Acceptable ) {
      setText( KGlobal::locale()->formatDate( stringToDate( text() ).addDays( -1 ), true ) );
    }
    ev->accept();
    break;
  default:
    if ( ( ev->state() & ControlButton )
	 && ev->key() == Key_T ) {
      setText( KGlobal::locale()->formatDate( QDate::currentDate(), true ) );
      ev->accept();
    }
    else {
      QLineEdit::keyPressEvent( ev );
    }
  }
}












