/***************************************************************************
	sqloutputhtml.cpp  -  description
	-------------------
	begin                : Fre Mär 8 2002
	copyright            : (C) 2002 by Harald Fernengel
	email                : harry@kdevelop.org
 ***************************************************************************/

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

#include <qsqldatabase.h>
#include <qsqlerror.h>
#include <qsqldriver.h>
#include <qstringlist.h>
#include <qsqlrecord.h>
#include <qsqlcursor.h>
#include <qstylesheet.h>
#include <qregexp.h>

#ifdef DEBUG
// only for outputting the library paths to developers on error
#include "sql.h"
#include <kapplication.h>
#endif

#include "sqloutputhtml.h"

// encodes a string as HTML
static QString enc( const QString& src, bool encLinefeeds = true )
{
  QString res;
  for ( uint i = 0; i < src.length(); ++i ) {
	ushort uc = src[ i ].unicode();
	if ( ( uc > 64 && uc < 127 ) ||        // character
		 ( uc > 39 && uc < 60 ) ) {        // digit
	  res += src[ i ];
	} else if ( uc == 32 ) {               // space
	  res += "&nbsp;";
	} else if ( uc == 10 ) {               // linefeed
	  res += encLinefeeds ? "<br />" : "\n";
	} else {
	  res += "&#" + QString::number( uc ) + ";";
	}
  }
  return res;
}

SqlOutputHTML::SqlOutputHTML( const KURL& u, QSqlDatabase* sdb ): SqlOutputIface( u, sdb )
{
  db = sdb;
  url = u;
}

SqlOutputHTML::~SqlOutputHTML()
{
}

QString SqlOutputHTML::header( const QString& title )
{
  return QString::fromLatin1( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
		"<html>\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
		"\n<title>%1</title>\n</head>\n<body>\n" ).arg( title );
}

QString SqlOutputHTML::footer()
{
  return QString::fromLatin1( "\n</body>\n</html>\n" );
}

QString SqlOutputHTML::showTables()
{
  if ( !db ) {
	error( cannotOpenDatabase() );
	return QString::null;
  }

  QString out = header( i18n( "List of available tables for %1" ).arg( db->databaseName() ) );

  out += "<h1>" + i18n( "Database: %1" ).arg( db->databaseName() ) + "</h1>\n";
  QStringList list = db->tables();
  out += "<table border=\"1\">\n";
  if ( list.count() > 0 ) {
    out += "<tr>\n<td>\n" +
	 i18n( "Select a table" ) +
	 "\n</td><td>\n" +
	 i18n( "and an action" ) +
	 "</td></tr>\n"
	 "<tr>\n<td valign=\"top\">\n"
	 " <form action=\"" + url.url() + "\" method=\"get\">\n"
	 " <select name=\"table\" size=\"8\">\n";

    QString plgOpt = "  <option value=\"%1\">%2</option>\n";
    for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
      if ( it == list.begin() )
	out += QString( "  <option value=\"%1\" selected>%2</option>\n" ).arg( *it ).arg( *it );
      else
        out += plgOpt.arg( *it ).arg( *it );
    }

    out += " </select>\n"
	   "</td>\n"
	   " <td valign=\"top\">\n"
	   "  <input type=\"radio\" name=\"action\" value=\"showTable\" checked>Show contents</option><br>\n"
	   "  <input type=\"radio\" name=\"action\" value=\"editTable\">Edit contents</option><br>\n"
	   "  <input type=\"radio\" name=\"action\" value=\"showMetadata\">Show structure</option><br>\n"
	   "  <input type=\"radio\" name=\"action\" value=\"showInsertForm\">Insert new row</option><br>\n"
	   " <br>\n " + i18n( "Maximum rows per page:" ) + "<br>\n"
	   "  <select name=\"limit\">\n"
	   "    <option>10</option><option>20</option><option>30</option><option>40</option>"
	   "    <option selected>50</option><option>100</option><option value=\"-1\">" + i18n( "unlimited" ) +
	   "    </option>\n"
	   " </td></tr>\n<tr><td colspan=\"2\">\n"
	   " <input type=\"submit\" value = \"" + i18n( "submit" ) + "\">\n"
	   "</form>\n"
	   "</td>\n</tr>\n</table>\n<br><br>";
  }

  out += "<table><tr>\n<td colspan=\"2\">\n" +
         i18n( "or enter a SQL query" ) +
	 "</td>\n</tr>\n";
  out += "<tr><td colspan=\"2\">\n";
  QString form = "<FORM ACTION=\"%1\" METHOD=\"get\">\n"
	 "<TEXTAREA NAME=\"query\" cols=\"60\" rows=\"8\"></TEXTAREA>\n"
	 "</td></tr>\n<tr><td colspan=\"2\">\n"
	 "<INPUT TYPE=\"SUBMIT\" VALUE = \"%2\">\n"
	 "<INPUT TYPE=\"RESET\" VALUE = \"%3\">\n"
	 "</FORM>";
  out += form.arg( url.url() ).arg( i18n("submit") ).arg( i18n("clear") );
  out += "</td></tr></table>\n";

  out += footer();
  return out;
}

QString SqlOutputHTML::showQuery( const QString& q, bool editable, const QSqlIndex& pk, int limit, int limitOffset )
{
  int rowCount = 0;
  static const QRegExp re( "offset=\\-?\\+?\\d+" );
  QString out = header( i18n( "Results of Query" ) );

  if ( editable && pk.isEmpty() ) {
    error ( errorNoPrimaryKey() );
    return QString::null;
  }
  
  QSqlQuery query( 0, db );
#if (QT_VERSION >= 0x030100)
  query.setForwardOnly( true );
#endif
  query.exec( q );

  if ( !query.isActive() ) {
	QString errTxt = errorExecuteQuery() + "<br />\nQuery:\n<pre>\n" + query.lastQuery() + "\n</pre>\n";
	error( errTxt, query.lastError() );
	return QString::null;
  }

  if ( !query.isSelect() ) {
	out += i18n( "Query OK, rows affected: %1" ).arg( query.numRowsAffected() );
  } else {
	QSqlRecordInfo rec = query.driver()->recordInfo( query );
	uint count = rec.count();

	QString delStr = i18n( "Del" );
	QString editStr = i18n( "Edit" );

	if ( editable ) {
	  out += "<p><a href=\"" + url.path() + "?action=showInsertForm\">" + i18n("Insert row") + "</a>\n";
        }

	out += "<table border=\"1\" cellpadding=\"3\">\n <tr>\n";
	if ( editable ) {
	       out += "  <td><b>" + editStr + "</b></td>\n"
		      "  <td><b>" + delStr + "</b></td>\n";
	}	
	for ( QSqlRecordInfo::iterator it = rec.begin(); it != rec.end(); ++it ) {
	  out += "  <td><b>" + (*it).name() + "</b></td>\n";
	}
	out += " </tr>\n";
	QString delURL = url.path() + "?action=showDeleteForm";
	QString editURL = url.path() + "?action=showUpdateForm";
	while ( query.next() ) {
	  rowCount++;
	  out += " <tr>\n";
	  if ( editable ) {
	    out += "  <td><a href=\"" + editURL + generatePrimaryKey( query, pk ) + "\">" + editStr + "</td>\n";
	    out += "  <td><a href=\"" + delURL + generatePrimaryKey( query, pk ) + "\">" + delStr + "</td>\n";
	  }
	  for ( uint i = 0; i < count; ++i ) {
		out += "  <td>" + enc( query.value( i ).toString() ) + "</td>\n";
	  }
	  out += " </tr>\n";
	}
	out += "</table>\n";
	if ( rowCount == 0 ) {
	  out += "<p>" + emptyResultSet();
	}
	out += "\n<p>";
	
	// check for LIMIT and add next/prev links
	QString u = url.url();
	if ( !u.contains( "offset=", false ) ) {
	  u += "&offset=0";
	}
	if ( limit > 0 && limitOffset > 0 ) {
	  int ne = limitOffset - limit;
	  if ( ne <= 0 )
	    ne = -1;
	  u.replace( re, "offset=" + QString::number( ne ) );
	  out += "<a href=\"" + u + "\">Previous results</a>";
	}
	if ( limit > 0 && limitOffset > 0 && rowCount >= limit ) {
	  out += " | ";
	}
	if ( limit > 0 && rowCount >= limit ) {
	  int ne = limitOffset + limit;
	  u.replace( re, "offset=" + QString::number( ne ) );
	  out += "<a href=\"" + u + "\">Next results</a>";
	}
  }
  out += footer();
  return out;
}

QString SqlOutputHTML::showQuery( const QString& q )
{
  return showQuery( q, false );
}

QString SqlOutputHTML::showTable( const QString& tableName, int limit, int limitOffset )
{
  QString query = SqlOutputIface::generateSQLCommand( tableName, limit, limitOffset );
  return showQuery( query, false, QSqlIndex(), limit, limitOffset );
}

QString SqlOutputHTML::showEditableTable( const QString& tableName, int limit, int limitOffset )
{
  if ( !db ) {
    error( cannotOpenDatabase() );
    return QString::null;
  }
    
  QString query = SqlOutputIface::generateSQLCommand( tableName, limit, limitOffset );
  return showQuery( query, true, db->primaryIndex( tableName ), limit, limitOffset );
}

void SqlOutputHTML::error( const QString& msg, const QSqlError& err )
{
  QString out = header( i18n("SQL KIOSlave error page") );

  out += "<h2>" + i18n( "SQL KIOSlave: An error has occurred" ) + "</h2>\n";
  out += "<p>\n" +  msg + "\n</p>\n";
  if ( err.type() != QSqlError::None ) {
	out += i18n( "This is the error message from the driver:" );
	out += "<br />\n<pre>\n" + err.driverText() + "\n</pre>\n";
	out += i18n( "This is the error message from the database:" );
	out += "<br />\n<pre>\n" + err.databaseText() + "\n</pre>\n";
  }

  out += footer();
  lastErr = out;
}

QString SqlOutputHTML::success( const QString& msg )
{
  QString out = header( i18n("SQL KIOSlave success") );
  KURL destUrl( url.path() + "../" );
  destUrl.cleanPath();
  // TODO: nicer navigation
  out += "<p>\n" + enc( msg ) + "\n</p>\n"
	 "<p>\n"
	 "<a href=\"" + destUrl.path() + "\">" + i18n( "Back to main menu" ) + "</a>\n"
	 "</p>\n";
  out += footer();
  
  return out;
}


QString SqlOutputHTML::showStartPage()
{
  QString out;
  QString plugins;
  QStringList drivers = QSqlDatabase::drivers();

  qDebug( " n1: %s", drivers.join( " " ).ascii() );

  if ( drivers.isEmpty() ) {
	error( SqlOutputIface::noPluginsAvailableStr(), QSqlError() );
	return QString::null;
  }

  QString plgOpt = "<OPTION VALUE=\"%1\">%2</OPTION>";
  for ( QStringList::Iterator it = drivers.begin(); it != drivers.end(); ++it ) {
	plugins += plgOpt.arg( (*it) ).arg( (*it) );
  }

  out = header( i18n( "Welcome to the KDE Sql IOSlave" ) );

  out += "<P>" + i18n("Please enter the connection data:\n");
  out += "<FORM ACTION=\"" + url.protocol() + ":/\" METHOD=\"get\">\n";
  out += " <TABLE>\n"
		 "  <TR>\n"
		 "   <TD>Database driver:</TD>\n"
		 "   <TD><SELECT NAME=plugin>" + plugins + "</SELECT></TD>\n"
		 "  </TR>\n"
		 "  <TR>\n"
		 "   <TD>Host:</TD>\n"
		 "   <TD><INPUT NAME=\"host\" TYPE=\"text\" SIZE=\"30\" MAXLENGTH=\"255\" /></TD>\n"
		 "  </TR>\n"
		 "  <TR>\n"
		 "   <TD>Database:</TD>"
		 "   <TD><INPUT NAME=\"database\" TYPE=\"text\" SIZE=\"30\" MAXLENGTH=\"255\"></TD>\n"
		 "  </TR>\n"
		 " </TABLE>\n"
		 " <INPUT TYPE=\"SUBMIT\" VALUE = \"" + i18n( "connect" ) + "\">\n"
		 "</FORM>\n";

  out += footer();
  return out;
}

QString SqlOutputHTML::showMetaData( const QString& tablename )
{
  QString out;

  if ( !db ) {
	error( cannotOpenDatabase() );
	return QString::null;
  }

  QSqlRecordInfo inf = db->recordInfo( tablename );
  if ( inf.isEmpty() ) {
	error( ( errorExecuteQuery() ) );
	return QString::null;
  }

  out = header( i18n( "Displaying structure" ) );
  out += "<h2>" + i18n( "Fields for table %1" ).arg( "&quot;" + enc( tablename ) + "&quot;</h2>\n" );
  out += QString( "\n<TABLE border=\"1\" cellpadding=\"3\">\n"
				  " <TR>\n"
				  "  <TD><B>%1</B></TD>\n"
				  "  <TD><B>%2</B></TD>\n"
				  "  <TD><B>%3</B></TD>\n"
				  "  <TD><B>%4</B></TD>\n"
				  "  <TD><B>%5</B></TD>\n"
				  "  <TD><B>%6</B></TD>\n"
				  " </TR>\n"
				).arg( i18n( "Field Name" ) ).arg( i18n( "Type" ) ).arg( i18n( "Required" )
				).arg( i18n( "Length" ) ).arg( i18n( "Precision" ) ).arg( i18n( "Default value" ) );
  for ( QSqlRecordInfo::Iterator it = inf.begin(); it != inf.end(); ++it ) {
	QString rec = i18n( "unknown" );
	if ( (*it).isRequired() == 0 ) {
	  rec = i18n( "no" );
	} else if ( (*it).isRequired() > 0 ) {
	  rec = i18n( "yes" );
	}
	out += " <TR>\n"
		   "  <TD>" + enc( (*it).name() ) + "</TD>\n"
		   "  <TD>" + QVariant::typeToName( (*it).type() ) + "</TD>\n"
		   "  <TD>" + rec + "</TD>\n"
		   "  <TD>" + QString::number( (*it).length() ) + "</TD>\n"
		   "  <TD>" + QString::number( (*it).precision() ) + "</TD>\n"
		   "  <TD>" + enc( (*it).defaultValue().toString() ) + "</TD>\n"
		   " </TR>";
  }
  out += "</TABLE>\n";

  QSqlIndex idx = db->primaryIndex( tablename );
  
  if ( idx.count() == 0 ) {
    out += "<p>" + i18n( "No primary index" );
  } else {
    out += "<h2>" + i18n( "Primary index: ") + "</h2>\n";
    out += "<TABLE border=\"1\" cellpadding=\"3\">\n"
	   " <TR>\n"
	   "  <TD><B>" + i18n("Field Name") + "</B></TD>\n"
	   "  <TD><B>" + i18n("Order") + "</B></TD>\n"
	   " </TR>\n";
    for ( int i = 0; i < (int)idx.count(); ++i ) {
      out += "<TR><TD>" + enc( idx.fieldName( i ) ) + "</TD><TD>";
      if ( idx.isDescending( i ) )
        out += i18n( "DESC" );
      else
        out += i18n( "ASC" );
      out += "</TD></TR>\n";
    }
    out += "</TABLE>\n";
  }
  
  out += footer();

  return out;
}

QString SqlOutputHTML::showInsertForm( const QString& table )
{
  QString out;
  
  if ( table.isEmpty() ) {
    error ( i18n( "Please specify a table" ) );
    return QString::null;
  }
  
  QSqlRecordInfo inf = db->recordInfo( table );
  if ( inf.isEmpty() ) {
    error ( errorExecuteQuery() );
    return QString::null;
  }

  out = header( i18n( "SQL KIOSlave: %1" ).arg( table ) );
  
  out += "<H1>" + i18n( "Table: %1" ).arg( table ) + "</H1>\n";
  
  out += "<FORM ACTION=\"" + url.url() + "\" METHOD=\"get\">\n"
         " <INPUT TYPE=\"HIDDEN\" NAME=\"action\" VALUE=\"insert\"></INPUT>\n"
         "<TABLE>\n";
         
  for ( QSqlRecordInfo::iterator it = inf.begin(); it != inf.end(); ++it ) {
    int msize = (*it).length();
    if ( msize <= 0 )
      msize = 255; // stupid default value if fieldlength is unknown
    out += "<TR>\n <TD>" + (*it).name() + "</TD>\n <TD>";
    out += QString( " <INPUT TYPE=\"TEXT\" NAME=\"_field%1\" MAXLENGTH=\"%2\">" ).arg( 
		    QStyleSheet::escape( (*it).name() ) ).arg( msize );
    out += "</INPUT><BR>\n"
           "</TD>\n</TR>\n";
  }

  out += QString ( "</TABLE>\n"
                   "<INPUT TYPE=\"SUBMIT\" VALUE = \"%2\">\n"
                   "<INPUT TYPE=\"RESET\" VALUE = \"%3\">\n"
                   "</FORM>\n" ).arg( i18n( "insert" ) ).arg( i18n( "clear values" ) );
  
  out += footer();
  
  return out;  
}

QString SqlOutputHTML::showDeleteForm( const QString& table, const QStringList& pks )
{
  QString out;
  if ( table.isEmpty() ) {
    error ( i18n( "Please specify a table" ) );
    return QString::null;
  }

  QSqlCursor cur( table, true, db );
  QSqlIndex rec = cur.primaryIndex( false );

  if ( rec.isEmpty() ) {
    error ( errorExecuteQuery() + i18n( " Could not get primary Index for %1" ).arg( table ) );
    return QString::null;
  }

  QString oldFields;
  for ( QStringList::ConstIterator it = pks.begin(); it != pks.end(); ++it ) {
    int pos = (*it).find( "=" );
    if ( pos <= 6 || !(*it).startsWith( "_field" ) )
      continue;
    QString fName = (*it).mid( 6, pos - 6 );
    QString fValue = (*it).mid( pos + 1 );
    cur.setValue( fName, fValue );
    oldFields += QString (" <INPUT TYPE=\"HIDDEN\" NAME=\"%1\" VALUE=\"%2\"></INPUT>" ).arg (
                 "_pkfield" + fName ).arg( fValue );
  }
  
  if ( !cur.select( rec, cur.primaryIndex( false ) ) ) {
    error ( errorExecuteQuery() + i18n( " Could not access %1" ).arg( table ) );
    return QString::null;
  }

  if ( !cur.next() ) {
    error ( errorExecuteQuery() + i18n( " Could not fetch record from %1" ).arg( table ) );
    return QString::null;
  }

  out = header( i18n( "SQL KIOSlave: %1" ).arg( table ) );

  out += "<H1>" + i18n( "Table: %1" ).arg( table ) + "</H1>\n";
  out += "<p>" + i18n("Do you really want to delete the selected record?");

  out += "<P><FORM ACTION=\"" + url.path() + "\" METHOD=\"get\">\n"
         " <INPUT TYPE=\"HIDDEN\" NAME=\"action\" VALUE=\"delete\"></INPUT>\n";
  out += oldFields;
  out += "<TABLE>\n"
         "<TR>\n"
         " <TD><b>" + i18n( "Fieldname" ) + "</b></TD>\n"
         " <TD><b>" + i18n( "Value" ) + "</b></TD>\n"
         "</TR>\n";

  QSqlRecordInfo inf = db->recordInfo( table );
  for ( QSqlRecordInfo::iterator it = inf.begin(); it != inf.end(); ++it ) {
    int msize = (*it).length();
    if ( msize <= 0 )
      msize = 255; // stupid default value if fieldlength is unknown
    out += "<TR>\n <TD>" + (*it).name() + "</TD>\n";
    out += " <TD>" + enc( cur.value( (*it).name() ).toString() ) +
           "</TD>\n</TR>\n";
  }

  out += QString ( "</TABLE>\n"
                   "<INPUT TYPE=\"SUBMIT\" VALUE = \"%2\">\n"
                   "</FORM>\n" ).arg( i18n( "delete" ) );

  out += footer();

  return out;
}


QString SqlOutputHTML::showUpdateForm( const QString& table, const QStringList& pks )
{
  QString out;

  if ( table.isEmpty() ) {
    error ( i18n( "Please specify a table" ) );
    return QString::null;
  }

  QSqlCursor cur( table, true, db );
  QSqlIndex rec = cur.primaryIndex( false );
    
  if ( rec.isEmpty() ) {
    error ( errorExecuteQuery() + i18n( " Could not get primary Index for %1" ).arg( table ) );
    return QString::null;
  }
  
  QString oldFields;
  for ( QStringList::ConstIterator it = pks.begin(); it != pks.end(); ++it ) {
    int pos = (*it).find( "=" );
    if ( pos <= 6 || !(*it).startsWith( "_field" ) )
      continue;
    QString fName = (*it).mid( 6, pos - 6 );
    QString fValue = (*it).mid( pos + 1 );
    cur.setValue( fName, fValue );
    oldFields += QString (" <INPUT TYPE=\"HIDDEN\" NAME=\"%1\" VALUE=\"%2\"></INPUT>" ).arg (
		 "_pkfield" + fName ).arg( fValue );
  }
  
  if ( !cur.select( rec, cur.primaryIndex( false ) ) ) {
    error ( errorExecuteQuery() + i18n( " Could not access %1" ).arg( table ) );
    return QString::null;
  }
  
  if ( !cur.next() ) {
    error ( errorExecuteQuery() + i18n( " Could not fetch record from %1" ).arg( table ) );
    return QString::null;
  }

  out = header( i18n( "SQL KIOSlave: %1" ).arg( table ) );
  
  out += "<H1>" + i18n( "Table: %1" ).arg( table ) + "</H1>\n";

  out += "<FORM ACTION=\"" + url.path() + "\" METHOD=\"get\">\n"
         " <INPUT TYPE=\"HIDDEN\" NAME=\"action\" VALUE=\"update\"></INPUT>\n";
  out += oldFields;
  out += "<TABLE>\n"
	 "<TR>\n"
	 " <TD><b>" + i18n( "Fieldname" ) + "</b></TD>\n"
	 " <TD><b>" + i18n( "New Value" ) + "</b></TD>\n"
	 " <TD><b>" + i18n( "Old Value" ) + "</b></TD>\n"
	 "</TR>\n";
  
  QSqlRecordInfo inf = db->recordInfo( table );       
  for ( QSqlRecordInfo::iterator it = inf.begin(); it != inf.end(); ++it ) {
    int msize = (*it).length();
    if ( msize <= 0 )
      msize = 255; // stupid default value if fieldlength is unknown
    out += "<TR>\n <TD>" + (*it).name() + "</TD>\n <TD>";
    out += QString( " <INPUT TYPE=\"TEXT\" NAME=\"_field%1\" MAXLENGTH=\"%2\" VALUE=\"%3\"></INPUT>" ).arg(
		    QStyleSheet::escape( (*it).name() ) ).arg( msize ).arg( enc( cur.value( (*it).name() ).toString(), false ) );
    out += "<BR>\n"
           "</TD>\n<TD>" + enc( cur.value( (*it).name() ).toString() ) +
	   "</TD>\n</TR>\n";
  }

  out += QString ( "</TABLE>\n"
                   "<INPUT TYPE=\"SUBMIT\" VALUE = \"%2\">\n"
                   "<INPUT TYPE=\"RESET\" VALUE = \"%3\">\n"
                   "</FORM>\n" ).arg( i18n( "update" ) ).arg( i18n( "clear values" ) );

  out += footer();

  return out;
}

QString SqlOutputHTML::showSpecial( const QString& action, const QString& table, const QStringList& params, int limit, int limitOffset )
{
  if ( action == "editTable" ) {
    return showEditableTable( table, limit, limitOffset );
  } else if ( action == "showInsertForm" ) {
    return showInsertForm( table );
  } else if ( action == "showDeleteForm" ) {
    return showDeleteForm( table, params );
  } else if ( action == "showUpdateForm" ) {
    return showUpdateForm( table, params );
  } else {
    error( SqlOutputIface::showSpecial( action, table ) );
    return lastError();
  }    
}
