/***************************************************************************
 *   Copyright (C) 2002 by Jakob Simon-Gaarde                              *
 *   jsgaarde@tdcspace.dk                                                  *
 *   Copyright (C) 2003 by Alexander Dymo                                  *
 *   cloudtemple@mksat.net                                                 *
 *                                                                         *
 *   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 "subclassingdlg.h"
#include "cppsupportpart.h"
#include "backgroundparser.h"
#include "store_walker.h"
#include "cppsupportfactory.h"
#include "kdevsourceformatter.h"
#include "kdevapi.h"
#include "kdevproject.h"
#include "filetemplate.h"
#include "codemodel.h"

#include <tqradiobutton.h>
#include <tqstringlist.h>
#include <tqcheckbox.h>
#include <tqmessagebox.h>
#include <tdefiledialog.h>
#include <klineedit.h>
#include <tqpushbutton.h>
#include <domutil.h>
#include <tqdom.h>
#include <kstandarddirs.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tqfile.h>
#include <tqregexp.h>
#include <tdeconfig.h>


#define WIDGET_CAPTION_NAME "widget/property|name=caption/string"
#define WIDGET_CLASS_NAME   "class"
#define WIDGET_SLOTS        "slots"
#define WIDGET_FUNCTIONS    "functions"

// All widgets
#define SLOT_ACCEPT SlotItem(m_slotView,"accept()","virtual","protected","void",false,true)
#define SLOT_REJECT SlotItem(m_slotView,"reject()","virtual","protected","void",false,true)

// Wizards
#define SLOT_BACK SlotItem(m_slotView,"back()","virtual","protected","void",false,true)
#define SLOT_NEXT SlotItem(m_slotView,"next()","virtual","protected","void",false,true)
#define SLOT_HELP SlotItem(m_slotView,"help()","virtual","protected","void",false,true)


SlotItem::SlotItem(TQListView *parent,const TQString &methodName,
                   const TQString &specifier,
                   const TQString &access, const TQString &returnType,
                   bool isFunc,bool callBaseClass)
: TQCheckListItem(parent,methodName,TQCheckListItem::CheckBox)
{
	setOn(true);
	m_methodName = methodName;
	m_access = access.isEmpty() ? (const TQString) "public" : access;
	m_specifier = specifier.isEmpty() ? (const TQString) "virtual" : specifier;
	m_returnType = returnType.isEmpty() ? (const TQString) "void" : returnType;
	m_isFunc = isFunc;
	m_callBaseClass = callBaseClass;
	setText(0,m_methodName);
	setText(1,m_access);
	setText(2,m_specifier);
	setText(3,m_returnType);
	setText(4,m_isFunc ? "Function" : "Slot");
	if (m_access=="private" || m_specifier=="non virtual")
	{
		setOn(false);
		setEnabled(false);
	}
	if (m_specifier=="pure virtual")
	{
		setOn(true);
		setEnabled(false);
	}
	m_alreadyInSubclass = false;
}

void SlotItem::setAllreadyInSubclass()
{
	setOn(true);
	setEnabled(false);
	m_alreadyInSubclass = true;
}


SubclassingDlg::SubclassingDlg(CppSupportPart* cppSupport, const TQString &formFile,
                               TQStringList &newFileNames, TQWidget* parent,
                               const char* name,bool modal, WFlags fl)
: SubclassingDlgBase(parent,name,modal,fl),
m_newFileNames(newFileNames), m_cppSupport( cppSupport )

{
	m_formFile = formFile;
	readUiFile();
	m_creatingNewSubclass = true;

	TDEConfig *config = CppSupportFactory::instance()->config();
	if (config)
	{
		config->setGroup("Subclassing");
		reformatDefault_box->setChecked(config->readBoolEntry("Reformat Source", 0));
		if (reformatDefault_box->isChecked())
			reformat_box->setChecked(true);
	}
}


SubclassingDlg::SubclassingDlg(CppSupportPart* cppSupport, const TQString &formFile,
                               const TQString &filename, TQStringList &dummy,
                               TQWidget* parent, const char* name, bool modal, WFlags fl)
: SubclassingDlgBase(parent, name, modal, fl),
m_newFileNames(dummy), m_cppSupport( cppSupport )

{
	m_formFile = formFile;
	m_creatingNewSubclass = false;
	m_filename = filename;

	TDEConfig *config = CppSupportFactory::instance()->config();
	if (config)
	{
		config->setGroup("Subclassing");
		reformatDefault_box->setChecked(config->readBoolEntry("Reformat Source", 0));
		if (reformatDefault_box->isChecked())
			reformat_box->setChecked(true);
	}

	TQStringList pathsplit(TQStringList::split('/',filename));

	TQString baseClass = readBaseClassName();
	if (!cppSupport->codeModel()->hasFile(filename+TQString(".h")))
		return;
	ClassList myClasses = cppSupport->codeModel()->fileByName(filename+TQString(".h"))->classList();
	for (ClassList::const_iterator classIt = myClasses.begin(); classIt != myClasses.end(); ++classIt)
	{
		kdDebug() << "base class " << baseClass << " class " << (*classIt)->name()
			<< " parents " << (*classIt)->baseClassList().join(",") << endl;
		if ( (*classIt)->baseClassList().findIndex(baseClass) != -1 )
		{
			kdDebug() << "base class matched " << endl;
			m_edClassName->setText((*classIt)->name());
			m_edFileName->setText(pathsplit[pathsplit.count()-1]);

			FunctionList functionList = (*classIt)->functionList();
			for (FunctionList::const_iterator methodIt = functionList.begin();
			     methodIt != functionList.end(); ++methodIt)
			{
				m_parsedMethods << (*methodIt)->name() + "(";
			}
		}
	}
	readUiFile();
	m_btnOk->setEnabled(true);
}

bool SubclassingDlg::alreadyInSubclass(const TQString &method)
{
	for (uint i=0;i<m_parsedMethods.count();i++)
	{
		if (method.find(m_parsedMethods[i])==0)
			return true;
	}
	return false;
}

void SubclassingDlg::readUiFile()
{
	TQStringList splitPath = TQStringList::split('/',m_formFile);
	m_formName = TQStringList::split('.',splitPath[splitPath.count()-1])[0]; // "somedlg.ui" = "somedlg"
	splitPath.pop_back();
	m_formPath = "/" + splitPath.join("/"); // join path to ui-file

	m_btnOk->setEnabled(false);
	TQDomDocument doc;

	DomUtil::openDOMFile(doc,m_formFile);
	m_baseClassName = DomUtil::elementByPathExt(doc,WIDGET_CLASS_NAME).text();

	m_baseCaption = DomUtil::elementByPathExt(doc,WIDGET_CAPTION_NAME).text();
	setCaption(i18n("Create Subclass of ")+m_baseClassName);

  // Special widget specific slots
	SlotItem *newSlot;
	m_qtBaseClassName = DomUtil::elementByPathExt(doc,"widget").attribute("class",TQDIALOG_OBJECT_NAME_STRING);

	if ( (m_qtBaseClassName==TQMAINWINDOW_OBJECT_NAME_STRING) || (m_qtBaseClassName==TQWIDGET_OBJECT_NAME_STRING) )
		m_canBeModal = false;
	else
		m_canBeModal = true;
	if (m_qtBaseClassName != TQWIDGET_OBJECT_NAME_STRING)
	{
		newSlot = new SLOT_ACCEPT;
		newSlot->setOn(false);
		if (alreadyInSubclass("accept()"))
			newSlot->setAllreadyInSubclass();
		m_slotView->insertItem(newSlot);
		m_slots << newSlot;

		newSlot = new SLOT_REJECT;
		newSlot->setOn(false);
		if (alreadyInSubclass("reject()"))
			newSlot->setAllreadyInSubclass();
		m_slotView->insertItem(newSlot);
		m_slots << newSlot;
	}

	if (m_qtBaseClassName == TQWIZARD_OBJECT_NAME_STRING)
	{
		newSlot = new SLOT_NEXT;
		m_slotView->insertItem(newSlot);
		if (alreadyInSubclass("next()"))
			newSlot->setAllreadyInSubclass();
		m_slots << newSlot;
		newSlot = new SLOT_BACK;
		m_slotView->insertItem(newSlot);
		if (alreadyInSubclass("back()"))
			newSlot->setAllreadyInSubclass();
		m_slots << newSlot;
		newSlot = new SLOT_HELP;
		newSlot->setOn(false);
		if (alreadyInSubclass("help()"))
			newSlot->setAllreadyInSubclass();
		m_slotView->insertItem(newSlot);
		m_slots << newSlot;
	}

	TQDomElement slotsElem = DomUtil::elementByPathExt(doc,WIDGET_SLOTS);
	TQDomNodeList slotnodes = slotsElem.childNodes();

	for (unsigned int i=0; i<slotnodes.count();i++)
	{
		TQDomElement slotelem = slotnodes.item(i).toElement();
		newSlot = new SlotItem(m_slotView,slotelem.text(),
		                       slotelem.attributeNode("specifier").value(),
		                       slotelem.attributeNode("access").value(),
		                       slotelem.attributeNode("returnType").value(),false);
		m_slotView->insertItem(newSlot);
		if (alreadyInSubclass(slotelem.text()))
			newSlot->setAllreadyInSubclass();
		m_slots << newSlot;
	}

	TQDomElement funcsElem = DomUtil::elementByPathExt(doc,WIDGET_FUNCTIONS);
	TQDomNodeList funcnodes = funcsElem.childNodes();
	SlotItem *newFunc;
	for (unsigned int i=0; i<funcnodes.count();i++)
	{
		TQDomElement funcelem = funcnodes.item(i).toElement();
		newFunc = new SlotItem(m_slotView,funcelem.text(),
		                       funcelem.attributeNode("specifier").value(),
		                       funcelem.attributeNode("access").value(),
		                       funcelem.attributeNode("returnType").value(),true);
		m_slotView->insertItem(newFunc);
		if (alreadyInSubclass(funcelem.text()))
			newFunc->setAllreadyInSubclass();
		m_slots << newFunc;
	}
}

SubclassingDlg::~SubclassingDlg()
{
}


void SubclassingDlg::updateDlg()
{
}

void SubclassingDlg::replace(TQString &string, const TQString& search, const TQString& replace)
{
	int nextPos = string.find(search);
	unsigned int searchLength = search.length();
	while (nextPos>-1)
	{
		string = string.replace(nextPos,searchLength,replace);
		nextPos = string.find(search,nextPos+replace.length());
	}
}

bool SubclassingDlg::loadBuffer(TQString &buffer, const TQString& filename)
{
  // open file and buffer it
	TQFile dataFile(filename);
	if (!dataFile.open(IO_ReadOnly))
		return false;
	char *temp = new char[dataFile.size()+1];
	dataFile.readBlock(temp,dataFile.size());
	temp[dataFile.size()]='\0';
	buffer = temp;
	delete [] temp;
	dataFile.close();
	return true;
}

bool SubclassingDlg::replaceKeywords(TQString &buffer,bool canBeModal)
{
	replace(buffer,"$NEWFILENAMEUC$",m_edFileName->text().upper());
	replace(buffer,"$BASEFILENAMELC$",m_formName.lower());
	replace(buffer,"$BASEFILENAME$",m_formName);
	replace(buffer,"$NEWCLASS$",m_edClassName->text());
	replace(buffer,"$TQTBASECLASS$", m_qtBaseClassName );
	replace(buffer,"$BASECLASS$",m_baseClassName);
	replace(buffer,"$NEWFILENAMELC$",m_edFileName->text().lower());
	if (canBeModal)
	{
		replace(buffer,"$CAN_BE_MODAL_H$",", bool modal = FALSE");
		replace(buffer,"$CAN_BE_MODAL_CPP1$",", bool modal");
		replace(buffer,"$CAN_BE_MODAL_CPP2$",", modal");
	}
	else
	{
		replace(buffer,"$CAN_BE_MODAL_H$","");
		replace(buffer,"$CAN_BE_MODAL_CPP1$","");
		replace(buffer,"$CAN_BE_MODAL_CPP2$","");
	}

	return true;
}

bool SubclassingDlg::saveBuffer(TQString &buffer, const TQString& filename)
{
	// save buffer

	TQFile dataFile(filename);
	if (!dataFile.open(IO_WriteOnly | IO_Truncate))
		return false;
	dataFile.writeBlock((buffer+"\n").ascii(),(buffer+"\n").length());
	dataFile.close();
	return true;
}


void SubclassingDlg::accept()
{
	TDEConfig *config = CppSupportFactory::instance()->config();
	if (config)
	{
		config->setGroup("Subclassing");
		config->writeEntry("Reformat Source", reformatDefault_box->isChecked());
	}

	unsigned int i;

  // h - file

	TQString public_slot =
		"/*$PUBLIC_SLOTS$*/\n  ";

	TQString protected_slot =
		"/*$PROTECTED_SLOTS$*/\n  ";

	TQString public_func =
		"/*$PUBLIC_FUNCTIONS$*/\n  ";

	TQString protected_func =
		"/*$PROTECTED_FUNCTIONS$*/\n  ";

	TQString buffer;
	int qtVersion = DomUtil::readIntEntry( *m_cppSupport->project()->projectDom(), "/kdevcppsupport/qt/version", 3 );
	if (m_creatingNewSubclass)
	{
		if ( qtVersion == 3 )
			loadBuffer(buffer,::locate("data", "kdevcppsupport/subclassing/subclass_template.h"));
		else
			loadBuffer(buffer,::locate("data", "kdevcppsupport/subclassing/subclass_qt4_template.h"));

		buffer = FileTemplate::read(m_cppSupport, "h") + buffer;
		TQFileInfo fi(m_filename + ".h");
		TQString module = fi.baseName();
		TQString basefilename = fi.baseName(true);
		buffer.replace(TQRegExp("\\$MODULE\\$"),module);
		buffer.replace(TQRegExp("\\$FILENAME\\$"),basefilename);
	}
	else
		loadBuffer(buffer,m_filename+".h");

	replaceKeywords(buffer,m_canBeModal);
	for (i=0; i<m_slots.count(); i++)
	{
		SlotItem *slitem = m_slots[i];
		if (!slitem->isOn() ||
		    slitem->m_alreadyInSubclass)
			continue;
		TQString declBuild;
		if (slitem->m_access=="public")
			if (!slitem->m_isFunc)
				declBuild = public_slot;
		else
			declBuild = public_func;
		if (slitem->m_access=="protected")
			if (!slitem->m_isFunc)
				declBuild = protected_slot;
		else
			declBuild = protected_func;
		if (!(slitem->m_specifier=="non virtual"))
			declBuild += "virtual ";
		declBuild += slitem->m_returnType + " ";
		TQString spacer;
		if (slitem->m_access=="public")
		{
			if (!slitem->m_isFunc)
			{
				declBuild += spacer.fill(' ',43-declBuild.length()) + slitem->m_methodName + ";";
				replace(buffer,"/*$PUBLIC_SLOTS$*/",declBuild);
			}
			else
			{
				declBuild += spacer.fill(' ',47-declBuild.length()) + slitem->m_methodName + ";";
				replace(buffer,"/*$PUBLIC_FUNCTIONS$*/",declBuild);
			}
		}
		if (slitem->m_access=="protected")
		{
			if (!slitem->m_isFunc)
			{
				declBuild += spacer.fill(' ',46-declBuild.length()) + slitem->m_methodName + ";";
				replace(buffer,"/*$PROTECTED_SLOTS$*/",declBuild);
			}
			else
			{
				declBuild += spacer.fill(' ',50-declBuild.length()) + slitem->m_methodName + ";";
				replace(buffer,"/*$PROTECTED_FUNCTIONS$*/",declBuild);
			}
		}
	}

	if (reformat_box->isChecked())
	{
		KDevSourceFormatter *fmt = m_cppSupport->extension<KDevSourceFormatter>("TDevelop/SourceFormatter");
		if (fmt)
			buffer = fmt->formatSource(buffer);
	}

	if (m_creatingNewSubclass)
		saveBuffer(buffer,m_formPath + "/" + m_edFileName->text()+".h");
	else
		saveBuffer(buffer,m_filename+".h");

  // cpp - file

	TQString implementation =
		"/*$SPECIALIZATION$*/\n"
		"$RETURNTYPE$ $NEWCLASS$::$METHOD$\n"
		"{\n"
		"}\n";

	TQString implementation_callbase =
		"/*$SPECIALIZATION$*/\n"
		"$RETURNTYPE$ $NEWCLASS$::$METHOD$\n"
		"{\n"
		"  $TQTBASECLASS$::$METHOD$;\n"
		"}\n";


	if (m_creatingNewSubclass)
	{
		if ( qtVersion == 3 )
			loadBuffer(buffer,::locate("data", "kdevcppsupport/subclassing/subclass_template.cpp"));
		else
			loadBuffer(buffer,::locate("data", "kdevcppsupport/subclassing/subclass_qt4_template.cpp"));

		buffer = FileTemplate::read(m_cppSupport, "cpp") + buffer;
		TQFileInfo fi(m_filename + ".cpp");
		TQString module = fi.baseName();
		TQString basefilename = fi.baseName(true);
		buffer.replace(TQRegExp("\\$MODULE\\$"),module);
		buffer.replace(TQRegExp("\\$FILENAME\\$"),basefilename);
		if ( (m_cppSupport->project()) && (m_cppSupport->project()->options() & KDevProject::UsesAutotoolsBuildSystem))
			buffer += "\n#include \"$NEWFILENAMELC$.moc\"\n";
	}
	else
		loadBuffer(buffer,m_filename+".cpp");

	replaceKeywords(buffer,m_canBeModal);
	for (i=0; i<m_slots.count(); i++)
	{
		SlotItem *slitem = m_slots[i];
		if (!slitem->isOn() ||
		    slitem->m_alreadyInSubclass)
			continue;
	TQString impl = slitem->m_callBaseClass ? implementation_callbase : implementation;
		replace(impl,"$RETURNTYPE$",slitem->m_returnType);
		replace(impl,"$NEWCLASS$",m_edClassName->text());
		replace(impl,"$METHOD$", slitem->m_methodName);
		replace(impl,"$TQTBASECLASS$", m_qtBaseClassName);
		replace(buffer,"/*$SPECIALIZATION$*/",impl);
	}

	if (reformat_box->isChecked())
	{
		KDevSourceFormatter *fmt = m_cppSupport->extension<KDevSourceFormatter>("TDevelop/SourceFormatter");
		if (fmt)
			buffer = fmt->formatSource(buffer);
	}

	if (m_creatingNewSubclass)
		saveBuffer(buffer,m_formPath + "/" + m_edFileName->text()+".cpp");
	else
		saveBuffer(buffer,m_filename+".cpp");

	if (m_creatingNewSubclass)
	{
		m_newFileNames.append(m_formPath + "/" + m_edFileName->text()+".cpp");
		m_newFileNames.append(m_formPath + "/" + m_edFileName->text()+".h");
	}
	SubclassingDlgBase::accept();
}

void SubclassingDlg::onChangedClassName()
{
  m_edFileName->setText(m_edClassName->text().lower());
  if (m_edFileName->text().isEmpty() ||
      m_edClassName->text().isEmpty())
    m_btnOk->setEnabled(false);
  else
    m_btnOk->setEnabled(true);
}

TQString SubclassingDlg::readBaseClassName()
{
	TQDomDocument doc;
	DomUtil::openDOMFile(doc,m_formFile);
	return DomUtil::elementByPathExt(doc,WIDGET_CLASS_NAME).text();
}
