/***************************************************************************
 *   Copyright (C) 2005 by Jens Herden                                     *
 *   jens@kdewebdev.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.                                   *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/

#include "projectviewprojectconfig.h"
#include "projectviewconfig.h"
#include "projectviewpart.h"
#include "filelist_widget.h"
#include "toolbarguibuilder.h"

#include <tqtimer.h>
#include <tqpopupmenu.h>
#include <tqwhatsthis.h>
#include <tqvaluelist.h>
#include <tqmap.h>
#include <tqdir.h>
#include <tqdom.h>
#include <tqvbox.h>
#include <tqhbox.h>
#include <tqpoint.h>
#include <tqlayout.h>

#include <tdemainwindow.h>
#include <kinputdialog.h>
#include <tdelocale.h>
#include <tdeaction.h>
#include <kdialogbase.h>
#include <kiconloader.h>
#include <tdemessagebox.h>
#include <tdetexteditor/viewcursorinterface.h>
#include <tdetexteditor/encodinginterface.h>
#include <tdeparts/part.h>
#include <kxmlguibuilder.h>
#include <kdebug.h>
#include <tdeconfig.h>
#include <tdeapplication.h>

#include <kdevplugininfo.h>
#include <kdevgenericfactory.h>
#include <kdevpartcontroller.h>
#include <kdevproject.h>
#include <kdevcore.h>
#include <kdevmainwindow.h>
#include <configwidgetproxy.h>



typedef KDevGenericFactory<ProjectviewPart> projectviewFactory;
KDevPluginInfo data("kdevfilelist");
K_EXPORT_COMPONENT_FACTORY(libkdevfilelist, projectviewFactory(data))

#define GLOBALDOC_OPTIONS 1
#define PROJECTDOC_OPTIONS 2

ProjectviewPart::ProjectviewPart(TQObject *parent, const char *name, const TQStringList &/*args*/)
    : KDevPlugin(&data, parent, name ? name : "FileListPart")
{
    setInstance(projectviewFactory::instance());
    setXMLFile("kdevfilelist.rc");
    setupActions();
    
    m_configProxy = new ConfigWidgetProxy(core());
    m_configProxy->createGlobalConfigPage(i18n("File List"), GLOBALDOC_OPTIONS, info()->icon());
    m_configProxy->createProjectConfigPage(i18n("File List"), PROJECTDOC_OPTIONS, info()->icon());

    connect(m_configProxy, TQ_SIGNAL(insertConfigWidget(const KDialogBase*, TQWidget*, unsigned int)),
            this, TQ_SLOT(insertConfigWidget(const KDialogBase*, TQWidget*, unsigned int)));
    
    connect(core(), TQ_SIGNAL(contextMenu(TQPopupMenu *, const Context *)),
            this, TQ_SLOT(contextMenu(TQPopupMenu *, const Context *)));
            
    connect(core(), TQ_SIGNAL(projectOpened()), this, TQ_SLOT(projectOpened()));
    connect(core(), TQ_SIGNAL(projectClosed()), this, TQ_SLOT(projectClosed()));
  
    TDEConfig * config = kapp->config();
    config->setGroup("File List Plugin");
    if (config->readBoolEntry("ToolbarInToolview", true))
    {
      m_toolbarWidget = new TQVBox(0, "toolbarContainer");
      m_toolbarWidget->setHidden(true);
      m_guibuilder = new ToolbarGUIBuilder(m_toolbarWidget, mainWindow()->main()); 
      setClientBuilder(m_guibuilder);
    }
    m_restored = false;
    TQTimer::singleShot(0, this, TQ_SLOT(init()));
}

ProjectviewPart::~ProjectviewPart()
{
  delete m_configProxy;
  delete m_guibuilder;
  if ( m_widget ) {
    mainWindow()->removeView( m_widget );
    delete m_widget;  // deletes the children as well
  }
}

void ProjectviewPart::restorePartialProjectSession(const TQDomElement * el)
{
    m_projectViews.clear(); // remove the global views
    m_restored = true;
    if (!el)
    {
        return;
    }
    // get the base of the project
    TQString urlStr = project()->projectDirectory();
    if (KURL::isRelativeURL(urlStr))
    {
        m_projectBase.setProtocol("file");
        m_projectBase.setPath(urlStr);
    } else
    {
        m_projectBase = KURL::fromPathOrURL(urlStr);
    }
    m_projectBase.adjustPath(+1);  // just in case
    
    // read all the views
    TQDomNodeList domList = el->elementsByTagName("projectview");
    
    uint len = domList.length();
    for (uint i = 0; i < len; ++i)
    {
        const TQDomElement viewEl = domList.item(i).toElement();
        if (viewEl.isNull())
        {
            continue;
        }
        FileInfoList urlList;
        TQDomNodeList fileList = viewEl.elementsByTagName("file");

        uint len2 = fileList.length();
        for (uint i2 = 0; i2 < len2; ++i2)
        {
            const TQDomElement fileEl = fileList.item(i2).toElement();
            if (!fileEl.isNull())
            {
                bool ok;
                int line = -1;
                TQString attr = fileEl.attribute("line");
                if (! attr.isNull()) 
                {
                    line = attr.toInt(&ok);
                    if (!ok)
                        line = -1;
                }
                int col = -1;
                attr = fileEl.attribute("col");
                if (! attr.isNull())
                {
                    col = attr.toInt(&ok);
                    if (!ok)
                        col = -1;
                }
                TQString encoding = "";
                attr = fileEl.attribute("encoding");
                if (! attr.isNull())
                {
                    encoding = attr;
                }
                TQString urlStr = fileEl.attribute("url");
                if (KURL::isRelativeURL(urlStr))
                {
                    KURL url = m_projectBase;
                    url.addPath(urlStr);
                    urlList.append(FileInfo(url, line, col, encoding));
                } else
                    urlList.append(FileInfo(KURL::fromPathOrURL(urlStr), line, col, encoding));
            }
        }
        m_projectViews.insert(viewEl.attribute("name"), urlList);
    }
    // read default view
    domList = el->elementsByTagName("defaultview");
    if (domList.length() > 0)
    {
        m_defaultProjectView = domList.item(0).toElement().attribute("name");
        if (!m_defaultProjectView.isEmpty()) 
        {
            slotOpenProjectView(m_defaultProjectView);
        } 
    } else 
    {
        m_defaultProjectView = "";
    }
}

void ProjectviewPart::savePartialProjectSession(TQDomElement * el)
{
    if (!el || m_projectViews.empty())
    {
        return;
    }
    TQDomDocument domDoc = el->ownerDocument();
    if (domDoc.isNull())
    {
        return;
    }
    // write all views
    ViewMap::ConstIterator it;
    for (it = m_projectViews.constBegin(); it != m_projectViews.constEnd(); ++it)
    {
        // we create the view even if there is no file inside, might be wanted by the user
        TQDomElement viewEl = domDoc.createElement("projectview");
        viewEl.setAttribute("name", it.key());
        el->appendChild(viewEl);
        for (FileInfoList::ConstIterator it2 = it.data().constBegin(); it2 != it.data().constEnd(); ++it2)
        {
            TQDomElement urlEl = domDoc.createElement("file");
            
            if (m_projectBase.isParentOf((*it2).url))
                urlEl.setAttribute("url", KURL::relativeURL(m_projectBase, (*it2).url));
            else
                urlEl.setAttribute("url", (*it2).url.url());
            urlEl.setAttribute("line", (*it2).line);
            urlEl.setAttribute("col", (*it2).col);
            urlEl.setAttribute("encoding", (*it2).encoding);
            viewEl.appendChild(urlEl);
        }
    }
    // write the default view
    TQDomElement defaultEl = domDoc.createElement("defaultview");
    defaultEl.setAttribute("name", m_defaultProjectView);
    el->appendChild(defaultEl);
}


void ProjectviewPart::init()
{
  // delayed initialization stuff goes here
  
  // create the toolview
  m_widget = new TQWidget(0, "filelist widget");
  m_widget->setIcon( SmallIcon(info()->icon()) );
  m_widget->setCaption(i18n("File List"));
  
  TQBoxLayout * l = new TQVBoxLayout(m_widget);
  
  // create the toolbar if needed
  if (m_guibuilder)
  {
    m_toolbarWidget->reparent(m_widget, TQPoint(0, 0), true);
    l->addWidget(m_toolbarWidget);
    TQWhatsThis::add(m_toolbarWidget, i18n("<b>View Session Toolbar</b><p>This allows to create and work with view sessions. A view session is a set of open documents.</p>"));
  }
  
  // create the listview 
  TQWidget * fileListWidget = new FileListWidget(this, m_widget);
  fileListWidget->setCaption(i18n("File List"));
  TQWhatsThis::add(fileListWidget, i18n("<b>File List</b><p>This is the list of opened files.</p>"));
  l->addWidget(fileListWidget);
  m_widget->setFocusProxy(fileListWidget);
  
  mainWindow()->embedSelectView(m_widget, i18n("File List"), i18n("Open files"));
  
  if (!project())
    readConfig();
}

void ProjectviewPart::setupActions()
{
    m_openPrjViewAction = new TDESelectAction(i18n("Open Session..."), 0, actionCollection(), "viewsession_open");
    
    connect(m_openPrjViewAction, TQ_SIGNAL(activated(const TQString &)), this, TQ_SLOT(slotOpenProjectView(const TQString &)));
    
    m_openPrjViewAction->setToolTip(i18n("Open Session"));
    
    m_savePrjViewAction = new TDEAction(i18n("Save Session"), "document-save", 0, this, TQ_SLOT(slotSaveProjectView()), actionCollection(), "viewsession_save");
    
    m_newPrjViewAction = new TDEAction(i18n("New Session..."), "document-new", 0, this, TQ_SLOT(slotSaveAsProjectView()), actionCollection(), "viewsession_new");
    
    m_deletePrjViewAction = new TDESelectAction(i18n("Delete Session"), "edit-delete", 0, actionCollection(), "viewsession_delete");
    
    connect(m_deletePrjViewAction, TQ_SIGNAL(activated(const TQString &)), this, TQ_SLOT(slotDeleteProjectView(const TQString &)));
    
    m_deletePrjViewAction->setToolTip(i18n("Delete Session"));
    
    m_deleteCurrentPrjViewAction = new TDEAction(i18n("Delete Session"), "edit-delete", 0, this, TQ_SLOT(slotDeleteProjectViewCurent()), actionCollection(), "viewsession_deletecurrent");
    
    m_deleteCurrentPrjViewAction->setToolTip(i18n("Delete Session"));
    
    adjustViewActions();
}

void ProjectviewPart::insertConfigWidget(const KDialogBase *dlg, TQWidget *page, unsigned int pageNo)
{
// create configuraton dialogs here
    switch (pageNo)
    {
        case PROJECTDOC_OPTIONS:
        {
            ProjectviewProjectConfig *w = new ProjectviewProjectConfig(this, page, "project config");
            connect(dlg, TQ_SIGNAL(okClicked()), w, TQ_SLOT(accept()));
            break;
        }
        case GLOBALDOC_OPTIONS:
        {
            ProjectviewConfig *w = new ProjectviewConfig(page, "global config");
            connect(dlg, TQ_SIGNAL(okClicked()), w, TQ_SLOT(accept()));
            break;
        }
    }
}

void ProjectviewPart::contextMenu(TQPopupMenu */*popup*/, const Context */*context*/)
{
// put actions into the context menu here
//     if (context->hasType(Context::EditorContext))
//     {
//         // editor context menu
//         const EditorContext *econtext = static_cast<const EditorContext*>(context);
// 
//         // use context and plug actions here
//         // action->plug(popup);
// 
//         // or create menu items on the fly
//         // int id = -1;
//         // id = popup->insertItem(i18n("Do Something Here"),
//         //     this, TQ_SLOT(doSomething()));
//         // popup->setWhatsThis(id, i18n("<b>Do something here</b><p>Describe here what does this action do."
//     }
//     else if (context->hasType(Context::FileContext))
//     {
//         // file context menu
//         const FileContext *fcontext = static_cast<const FileContext*>(context);
// 
//         //use context and plug actions here
//     }
//     else if (context->hasType(Context::ProjectModelItemContext))
//     {
//         // project tree context menu
//         const ProjectModelItemContext *pcontext = static_cast<const ProjectModelItemContext*>(context);
// 
//         // use context and plug actions here
//     }
//     else if (context->hasType(Context::CodeModelItemContext))
//     {
//         // class tree context menu
//         const CodeModelItemContext *mcontext = static_cast<const CodeModelItemContext*>(context);
// 
//         // use context and plug actions here
//     }
//     else if (context->hasType(Context::DocumentationContext))
//     {
//         // documentation viewer context menu
//         const DocumentationContext *dcontext = static_cast<const DocumentationContext*>(context);
// 
//         // use context and plug actions here
//     }
}

void ProjectviewPart::projectOpened()
{
    if ( !m_restored )
        m_projectViews.clear(); // remove the global views
    adjustViewActions();
    m_restored = false;
}

void ProjectviewPart::projectClosed()
{
    m_projectBase = KURL();
    m_defaultProjectView = "";
    readConfig();  // read the global project views
}

void ProjectviewPart::slotOpenProjectView(const TQString &view)
{
    TDEConfig * config = kapp->config();
    config->setGroup("File List Plugin");
    bool onlyProject = config->readBoolEntry("OnlyProjectFiles", false);
    bool closeOpenFiles = config->readBoolEntry("CloseOpenFiles", true);
    
    m_currentProjectView = view;
    
    if (m_projectViews.contains(view) > 0)
    {
        FileInfoList viewUrls = m_projectViews[view];
        
        if (closeOpenFiles)
        {
            // we close everything that is not part of the project view
            KURL::List urlsToClose = partController()->openURLs();
            for (KURL::List::Iterator it = urlsToClose.begin(); it != urlsToClose.end(); ++it)
            {
                // it is in the list of wanted files and do we want it at all
                if ((viewUrls.contains(*it) > 0) && (!onlyProject || !project() ||  project()->isProjectFile((*it).path()) ))
                {
                    viewUrls.remove(*it); // don't open if it is open already
                    it = urlsToClose.remove(it);
                    --it;  //  do not skip one
                }
            }
            if (!urlsToClose.empty())
            {
                partController()->closeFiles(urlsToClose);
            }
        }
        // we open what still needs to get opened
        FileInfoList::const_iterator viewIt;
        for (viewIt = viewUrls.begin(); viewIt != viewUrls.end(); ++viewIt)
        {
            if (!onlyProject || !project() || project()->isProjectFile((*viewIt).url.path()))
            {
                partController()->setEncoding( (*viewIt).encoding );
                partController()->editDocument((*viewIt).url, (*viewIt).line, (*viewIt).col); 
            }
        }
    }
    adjustViewActions();
}


void ProjectviewPart::slotSaveProjectView()
{
    slotSaveAsProjectView(m_currentProjectView.isEmpty());
}


void ProjectviewPart::adjustViewActions()
{
    TQStringList viewList = getViewList();
    
    m_openPrjViewAction->clear();
    m_openPrjViewAction->setItems(viewList);
    int i = viewList.findIndex(m_currentProjectView);
    if (i > -1)
    {
        m_openPrjViewAction->setCurrentItem(i);
    }
    m_deletePrjViewAction->clear();
    m_deletePrjViewAction->setItems(viewList);
    m_currentProjectView = m_openPrjViewAction->currentText();
    if (m_currentProjectView.isEmpty() && !viewList.empty())
    {
        m_currentProjectView = viewList.front();
    }
    bool haveView = !m_currentProjectView.isEmpty();
    m_savePrjViewAction->setEnabled(haveView);
    m_deleteCurrentPrjViewAction->setEnabled(haveView);
}


void ProjectviewPart::slotDeleteProjectViewCurent()
{
    slotDeleteProjectView(m_currentProjectView);
}

void ProjectviewPart::slotDeleteProjectView(const TQString& view)
{
    m_projectViews.remove(view);
    
    if (m_currentProjectView == view)
        m_currentProjectView = "";
    
    if (m_defaultProjectView == view)
        m_defaultProjectView = "";
    
    if (! project())
      writeConfig();

    adjustViewActions();
}


void ProjectviewPart::slotSaveAsProjectView(bool askForName)
{
    if (askForName)
    {
        bool ok;
        TQString newProjectView = KInputDialog::getText(i18n("Save View Session As"), i18n("Enter the name of the session:"), "", &ok, mainWindow()->main());
        if (!ok)
        {
            return;
        }
        newProjectView = newProjectView.remove("="); // we use this string in config files and = would confuse it
        if (m_projectViews.contains(newProjectView) > 0 &&
            KMessageBox::warningContinueCancel(mainWindow()->main(), i18n("<qt>A view session named <b>%1</b> already exists.<br>Do you want to overwrite it?</qt>").arg(newProjectView), TQString(), i18n("Overwrite")) != KMessageBox::Continue)
        {
            return;
        }
        m_currentProjectView = newProjectView;
    }
    
    FileInfoList viewUrls;
    KURL::List openURLs = partController()->openURLs();
    
    for (KURL::List::Iterator it = openURLs.begin(); it != openURLs.end(); ++it)
    {
        // test if we have an editor
        // FIXME this can fail if there are two parts with the same URL
        KParts::ReadOnlyPart *ro_part = partController()->partForURL(*it);
        KTextEditor::ViewCursorInterface* cursorIf = dynamic_cast<KTextEditor::ViewCursorInterface*>(ro_part->widget());
        if (cursorIf)
        {
            TQString encoding;
            if ( KTextEditor::EncodingInterface * ei = dynamic_cast<KTextEditor::EncodingInterface*>( ro_part ) )
            {
                TQString temp = ei->encoding();
                if ( !temp.isNull() )
                {
                    encoding = temp;
                }
            } 

            unsigned int line, col;
            cursorIf->cursorPositionReal(&line, &col);
            viewUrls.append(FileInfo(*it, line, col, encoding));
        }
    }
    // add or overwrite the values
    m_projectViews.insert(m_currentProjectView, viewUrls, true);
    if (! project())
      writeConfig();

    adjustViewActions();
}


void ProjectviewPart::writeConfig()
{
  TDEConfig * config = kapp->config();
  config->deleteGroup("ProjectViews", true);
  config->setGroup("ProjectViews");
    
    // write all views
  ViewMap::ConstIterator it;
  for (it = m_projectViews.constBegin(); it != m_projectViews.constEnd(); ++it)
  {
    // we create the view even if there is no file inside, might be wanted by the user
    TQStringList urls;
    for (FileInfoList::ConstIterator it2 = it.data().constBegin(); it2 != it.data().constEnd(); ++it2)
    {
      if ((*it2).encoding.isEmpty())
        urls.append((*it2).url.url());
      else
        urls.append((*it2).url.url() + ";" + (*it2).encoding);
    }
    config->writeEntry(it.key(), urls);
  }
}


void ProjectviewPart::readConfig()
{
  TDEConfig * config = kapp->config();
  TQMap<TQString, TQString> entries = config->entryMap("ProjectViews");
  
  m_projectViews.clear();
  TQMap<TQString, TQString>::ConstIterator it;
  for (it = entries.constBegin(); it != entries.constEnd(); ++it)
  {
    FileInfoList urlList;
    TQStringList urls = TQStringList::split(",", it.data());
    for (TQStringList::Iterator it2 = urls.begin(); it2 != urls.end(); ++it2 )
    {
      // search the encoding. The entry can be like: fileURL;encoding
      TQStringList file = TQStringList::split(";", *it2);
      if (file.count() == 1)
        urlList.append(FileInfo(KURL::fromPathOrURL(*it2)));
      else
        urlList.append(FileInfo(KURL::fromPathOrURL(file.first()), -1, -1, file.last()));
    }
    m_projectViews.insert(it.key(), urlList);
  }
  adjustViewActions();
}

#include "projectviewpart.moc"
