/*****
 *
 * TOra - An Oracle Toolkit for DBA's and developers
 * Copyright (C) 2003-2005 Quest Software, Inc
 * Portions Copyright (C) 2005 Other Contributors
 * 
 * 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;  only version 2 of
 * the License is valid for this program.
 * 
 * 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.
 *
 *      As a special exception, you have permission to link this program
 *      with the Oracle Client libraries and distribute executables, as long
 *      as you follow the requirements of the GNU GPL in regard to all of the
 *      software in the executable aside from Oracle client libraries.
 *
 *      Specifically you are not permitted to link this program with the
 *      Qt/UNIX, Qt/Windows or Qt Non Commercial products of TrollTech.
 *      And you are not permitted to distribute binaries compiled against
 *      these libraries without written consent from Quest Software, Inc.
 *      Observe that this does not disallow linking to the Qt Free Edition.
 *
 *      You may link this product with any GPL'd Qt library such as Qt/Free
 *
 * All trademarks belong to their respective owners.
 *
 *****/

#include "utils.h"

#include "tobrowsertable.h"
#include "toconnection.h"
#include "todatatype.h"
#include "toextract.h"
#include "tohighlightedtext.h"
#include "tomemoeditor.h"
#include "toresultcombo.h"
#include "tosql.h"

#include <qapplication.h>
#include <qcombobox.h>
#include <qgrid.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qlineedit.h>
#include <qprogressdialog.h>
#include <qscrollview.h>
#include <qtimer.h>

#include "tobrowsertable.moc"
#include "tobrowsertableui.moc"

static toSQL SQLListTablespaces("toBrowserTable:ListTablespaces",
				"SELECT Tablespace_Name FROM sys.DBA_TABLESPACES\n"
				" ORDER BY Tablespace_Name",
				"List the available tablespaces in a database.");

void toBrowserTable::editTable(toConnection &conn,const QString &owner,const QString &table,QWidget *parent)
{
  toBrowserTable dialog(conn,owner,table,parent);
  if (dialog.exec()) {
    std::list<toSQLParse::statement> statements=toSQLParse::parse(dialog.sql(),conn);
    try {
      QProgressDialog prog(tr("Performing table changes"),
			   tr("Executing table change script"),
			   statements.size(),
			   &dialog,"progress",true);
      prog.setCaption(tr("Performing table changes"));
      for(std::list<toSQLParse::statement>::iterator i=statements.begin();i!=statements.end();i++) {
	QString sql=toSQLParse::indentStatement(*i,conn);
	int i=sql.length()-1;
	while(i>=0&&(sql.at(i)==';'||sql.at(i).isSpace()))
	  i--;
	if (i>=0)
	  conn.execute(sql.mid(0,i+1));
	qApp->processEvents();
	if (prog.wasCancelled())
	  throw tr("Cancelled ongoing table modification, table might be corrupt");
      }
    } TOCATCH
  }
}

toBrowserTable::toBrowserTable(toConnection &conn,const QString &owner,const QString &table,QWidget *parent,const char *name)
  : toBrowserTableUI(parent,name,true),toConnectionWidget(conn,this),Extractor(conn,NULL)
{
  ColumnNumber=0;
  ColumnGrid=new QGrid(3,ColumnList->viewport());
  ColumnList->addChild(ColumnGrid);
  ColumnList->viewport()->setBackgroundColor(qApp->palette().active().background());

  new QLabel(tr("Name"),ColumnGrid);
  new QLabel(tr("Datatype"),ColumnGrid);
  new QLabel(tr("Extra parameters"),ColumnGrid);

  Extractor.setIndexes(false);
  Extractor.setConstraints(false);
  Extractor.setPrompt(false);
  Extractor.setHeading(false);

  UglyFlag=false; // Indicates wether the correct size has been retreived at least once.

  try {
    Owner=owner;
    Table=table;

    QString tablespace;
    Schema->query(toSQL::sql(toSQL::TOSQL_USERLIST));
    Schema->setSelected(Owner);

    if (!Table.isEmpty()) {
      std::list<QString> Objects;
      Objects.insert(Objects.end(),"TABLE:"+Owner+"."+Table);

      OriginalDescription=Extractor.describe(Objects);
      Schema->setEnabled(false);

      bool invalid=false;

      Name->setText(table);

      QString declarations;
      QString storage;
      QString parallel;

      for(std::list<QString>::iterator i=OriginalDescription.begin();i!=OriginalDescription.end();i++) {
	std::list<QString> row=toExtract::splitDescribe(*i);
	if (toShift(row)!=connection().quote(owner)) {
	  invalid=true;
	  break;
	}
	if (toShift(row)!="TABLE")
	  continue;
	if (toShift(row)!=connection().quote(table)) {
	  invalid=true;
	  break;
	}
	QString type=toShift(row);
	if (type=="COLUMN") {
	  // Nop, handled by the parseColumnDescription call below
	} else if (type=="COMMENT")
	  Comment->setText(toShift(row));
	else if (type=="STORAGE")
	  storage+=toShift(row)+" ";
	else if (type=="PARALLEL")
	  parallel+=toShift(row)+" ";
	else if (type=="PARAMETERS") {
	  QString t=toShift(row);
	  if (t.startsWith("TABLESPACE")) {
	    tablespace=connection().unQuote(t.mid(10).stripWhiteSpace());
	  } else
	    declarations+=t+" ";
	} else if (!type.isEmpty())
	  invalid=true;
      }

      Columns=toExtract::parseColumnDescription(OriginalDescription);

      ExtraDeclarations->setText(declarations.stripWhiteSpace());
      StorageDeclarations->setText(storage.stripWhiteSpace());
      ParallelDeclarations->setText(parallel.stripWhiteSpace());
      {
	for(unsigned int i=0;i<Columns.size();i++)
	  addColumn();
      }
      std::list<QLineEdit *>::iterator name=ColumnNames.begin();
      std::list<toDatatype *>::iterator datatype=Datatypes.begin();
      std::list<QLineEdit *>::iterator extra=Extra.begin();
      for(std::list<toExtract::columnInfo>::iterator column=Columns.begin();
	  name!=ColumnNames.end()&&datatype!=Datatypes.end()&&extra!=Extra.end()&&column!=Columns.end();
	  name++,datatype++,extra++,column++) {
	if ((*column).Order==0)
	  invalid=true;
	(*name)->setText((*column).Name);
	(*datatype)->setType((*column).Definition);
	(*extra)->setText((*column).Data["EXTRA"]);
      }
      if (invalid) {
	reject();
	toStatusMessage("Invalid output from extraction layer prevent this dialog to be properl filled in");
	return;
      }
    } else
      addColumn();
    if (toIsMySQL(connection())) {
      ParallelLabel->hide();
      ParallelDeclarations->hide();
      SchemaLabel->setText(tr("&Database"));
    }
    try {
      toQuery query(connection(),SQLListTablespaces);
      while(!query.eof()) {
	QString t=query.readValueNull();
	Tablespace->insertItem(t);
	if (t==tablespace)
	  Tablespace->setCurrentItem(Tablespace->count()-1);
      }
    } catch(...) {
      TablespaceLabel->hide();
      Tablespace->hide();
    }
  } catch(const QString &exc) {
    toStatusMessage(exc);
    reject();
  }
  uglyWorkaround();
}

void toBrowserTable::addParameters(std::list<QString> &migrateTable,
				   const std::list<QString> &ctx,
				   const QString &type,
				   const QString &data)
{
  toSQLParse::statement statement=toSQLParse::parseStatement(data,connection());
  std::list<toSQLParse::statement>::iterator beg=statement.subTokens().begin();
  std::list<toSQLParse::statement>::iterator end=beg;
  while(end!=statement.subTokens().end()) {
    if (beg!=end) {
      if ((*end).String!="=") {
	if ((*end).String=="(") {
	  do {
	    end++;
	  } while(end!=statement.subTokens().end()&&(*end).String!=")");
	}
	end++;
	toExtract::addDescription(migrateTable,ctx,type,Extractor.createFromParse(beg,end));
	beg=end;
      } else
	end++;
    } else
      end++;
  }
  if (beg!=end)
    toExtract::addDescription(migrateTable,ctx,type,Extractor.createFromParse(beg,end).stripWhiteSpace());
}

QString toBrowserTable::sql()
{
  std::list<QString> migrateTable;

  std::list<QString> ctx;
  toPush(ctx,Owner);
  toPush(ctx,QString("TABLE"));
  if (Table.isEmpty())
    toPush(ctx,Name->text());
  else
    toPush(ctx,Table);
  toExtract::addDescription(migrateTable,ctx);
  if (Name->text()!=Table&&!Table.isEmpty())
    toExtract::addDescription(migrateTable,ctx,"RENAME",Name->text());

  std::list<toExtract::columnInfo>::const_iterator column=Columns.begin();
  std::list<QLineEdit *>::const_iterator name=ColumnNames.begin();
  std::list<toDatatype *>::const_iterator datatype=Datatypes.begin();
  std::list<QLineEdit *>::const_iterator extra=Extra.begin();
  int num=1;
  while(name!=ColumnNames.end()&&datatype!=Datatypes.end()&&extra!=Extra.end()) {
    QString cname;
    if (column!=Columns.end()) {
      cname=(*column).Name;
      if ((*name)->text()!=(*column).Name)
	toExtract::addDescription(migrateTable,ctx,"COLUMN",cname,"RENAME",(*name)->text());
      column++;
    } else
      cname=(*name)->text();

    toExtract::addDescription(migrateTable,ctx,"COLUMN",cname);
    toExtract::addDescription(migrateTable,ctx,"COLUMN",cname,(*datatype)->type());
    if (!(*extra)->text().isEmpty())
      toExtract::addDescription(migrateTable,ctx,"COLUMN",cname,"EXTRA",(*extra)->text());
    toExtract::addDescription(migrateTable,ctx,"COLUMN",cname,"ORDER",QString::number(num++));
    name++;
    datatype++;
    extra++;
  }

  if (!Comment->text().isEmpty())
    toExtract::addDescription(migrateTable,ctx,"COMMENT",Comment->text());
  addParameters(migrateTable,ctx,"STORAGE",StorageDeclarations->text());
  addParameters(migrateTable,ctx,"PARALLEL",ParallelDeclarations->text());
  addParameters(migrateTable,ctx,"PARAMETERS",ExtraDeclarations->text());

  migrateTable.sort();

  return Extractor.migrate(OriginalDescription,migrateTable);
}

void toBrowserTable::displaySQL()
{
  toMemoEditor memo(this,sql(),-1,-1,true,true);
  memo.exec();
}

void toBrowserTable::addColumn()
{
  ColumnNumber++;
  QLineEdit *tl;
  toDatatype *td;
  tl=new QLineEdit(ColumnGrid,QString::number(ColumnNumber));
  tl->show();
  ColumnNames.insert(ColumnNames.end(),tl);
  td=new toDatatype(connection(),ColumnGrid);
  td->show();
  Datatypes.insert(Datatypes.end(),td);
  tl=new QLineEdit(ColumnGrid);
  tl->show();
  Extra.insert(Extra.end(),tl);
}

void toBrowserTable::removeColumn()
{
  // Not implemented yet
}

void toBrowserTable::toggleCustom(bool val)
{
  for(std::list<toDatatype *>::iterator i=Datatypes.begin();i!=Datatypes.end();i++)
    (*i)->setCustom(val);
}

void toBrowserTable::uglyWorkaround()
{
  // Somehome the size doesn't get updated until way later so just keep calling until it gets set.
  if (ColumnList->width()>220||UglyFlag) {
    ColumnGrid->setFixedWidth(ColumnList->width()-30);
    UglyFlag=true;
  } else
    QTimer::singleShot(100,this,SLOT(uglyWorkaround()));
}

void toBrowserTable::resizeEvent(QResizeEvent *e)
{
  toBrowserTableUI::resizeEvent(e);
  uglyWorkaround();
}
