/***************************************************************************
 *   Copyright (C) 2005-2006 Nicolas Hadacek <hadacek@kde.org>             *
 *   Copyright (C) 2003-2004 Alain Gibaud <alain.gibaud@free.fr>           *
 *                                                                         *
 *   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 "editor_manager.h"

#include <tqiconset.h>
#include <tqdragobject.h>
#include <tqpainter.h>

#include <klocale.h>
#include <kiconloader.h>
#include <kpopupmenu.h>
#include <kaction.h>

#include "text_editor.h"
#include "hex_editor.h"
#include "object_view.h"
#include "new_dialogs.h"
#include "main_global.h"
#include "gui_debug_manager.h"
#include "common/gui/purl_gui.h"
#include "device_gui.h"
#include "register_view.h"
#include "device_editor.h"
#include "project_manager.h"
#include "global_config.h"
#include "project.h"

//-----------------------------------------------------------------------------
SwitchToDialog::SwitchToDialog(const TQStringList &names, TQWidget *parent)
  : Dialog(parent, "switch_to_dialog", true, i18n("Switch to editor"), Ok | Cancel, Ok, false)
{
  TQVBoxLayout *top = new TQVBoxLayout(mainWidget(), 10, 10);
  _edit = new KLineEdit(mainWidget());
  _edit->setCompletedItems(names);
  top->addWidget(_edit);
}

//-----------------------------------------------------------------------------
void EditorTabBar::paintLabel(TQPainter *p, const TQRect &br, TQTab *t, bool has_focus) const
{
  TQFont f = p->font();
  f.setItalic(_readOnly[t]);
  p->setFont(f);
  TQTabBar::paintLabel(p, br, t, has_focus);
}

//-----------------------------------------------------------------------------
TQString EditorHistory::goBack()
{
  if ( !hasBack() ) return TQString();
  _current--;
  return _names[_current];
}

TQString EditorHistory::goForward()
{
  if ( !hasForward() ) return TQString();
  _current++;
  return _names[_current];
}

void EditorHistory::add(const TQString &name)
{
  if ( _names.count()!=0 ) {
    _current = TQMIN(_current, _names.count()-1);
    if ( _names[_current]==name ) return;
    if ( _current!=0 && _names[_current-1]==name ) {
      _current--;
      return;
    }
    _current++;
    if ( _current<_names.count() && _names[_current]==name ) return;
  }
  _names.resize(_current+1);
  _names[_current] = name;
}

void EditorHistory::closedLast()
{
  if ( _names.count()==0 ) return;
  _names.resize(_current);
  if ( _current!=0 ) _current--;
}

//-----------------------------------------------------------------------------
const char * const EditorManager::EDITOR_TAGS[Nb_EditorTypes] = { "device", "registers" };

EditorManager::EditorManager(TQWidget *parent)
  : TabWidget(parent, "editor_manager"), _current(0)
{
  setTabBar(new EditorTabBar(this));
  connect(this, TQT_SIGNAL(currentChanged(TQWidget *)), TQT_SLOT(showEditor(TQWidget *)));
  setHoverCloseButton(readConfigEntry(BaseGlobalConfig::ShowTabCloseButton).toBool());
  setHoverCloseButtonDelayed(false);
}

bool EditorManager::openFile(const PURL::Url &url)
{
  if ( url.isEmpty() ) return false;
  Editor *e = findEditor(url);
  if (e) { // document already loaded
    if ( !MessageBox::askContinue(i18n("File \"%1\" already loaded. Reload?").arg(url.kurl().prettyURL()),
                                 i18n("Warning"), i18n("Reload")) ) return true;
    if ( !e->slotLoad() ) {
      closeEditor(e, false);
      return false;
    }
    return true;
  }
  if ( !openEditor(url) ) {
    static_cast< TDERecentFilesAction *>(Main::action("file_open_recent"))->removeURL(url.kurl());
    return false;
  }
  static_cast<TDERecentFilesAction *>(Main::action("file_open_recent"))->addURL(url.kurl());
  return true;
}

void EditorManager::connectEditor(Editor *editor)
{
    disconnectEditor(currentEditor());
    if ( editor==0 ) return;
    editor->addGui();
    connect(editor, TQT_SIGNAL(modified()), TQT_SLOT(modifiedSlot()));
    connect(editor, TQT_SIGNAL(guiChanged()), TQT_SIGNAL(guiChanged()));
    connect(editor, TQT_SIGNAL(dropEventPass(TQDropEvent *)), TQT_SLOT(slotDropEvent(TQDropEvent *)));
    connect(editor, TQT_SIGNAL(statusTextChanged(const TQString &)), TQT_SIGNAL(statusChanged(const TQString &)));
}

void EditorManager::modifiedSlot()
{
  emit modified(currentEditor()->url());
}

void EditorManager::disconnectEditor(Editor *editor)
{
  if ( editor==0 ) return;
  editor->disconnect(this);
  editor->removeGui();
}

TQString EditorManager::title(const Editor &e) const
{
  return (e.url().isEmpty() ? "<" + e.name() + ">" : e.url().filename());
}

void EditorManager::updateTitles()
{
  KIconLoader loader;
  TQPixmap def = loader.loadIcon("piklab", KIcon::Small);
  TQPixmap modified = loader.loadIcon("filesave", KIcon::Small);
  TQPixmap chip = loader.loadIcon("piklab_chip", KIcon::Small);
  TQValueList<Editor *>::iterator it = _editors.begin();
  for (; it!=_editors.end(); ++it) {
    static_cast<EditorTabBar *>(tabBar())->setReadOnly(indexOf(*it), (*it)->isReadOnly());
    TQPixmap pixmap;
    if ( (*it)->isModified() ) pixmap = modified;
    else if ( ::tqqt_cast< ::DeviceEditor *>(*it)==0 ) pixmap = PURL::icon((*it)->fileType());
    else pixmap = chip;
    changeTab(*it, pixmap.isNull() ? def : pixmap, title(**it));
  }
}

bool EditorManager::closeCurrentEditor()
{
  if ( !closeEditor(_current, true) ) return false;
  emit guiChanged();
  return true;
}

bool EditorManager::closeAllEditors()
{
  if ( currentEditor()==0 ) return true;
  while ( currentEditor() )
    if ( !closeEditor(currentEditor(), true) ) break;
  emit guiChanged();
  return ( currentEditor()==0 );
}

bool EditorManager::closeAllOtherEditors()
{
  if ( nbEditors()==1 ) return true;
  TQValueList<Editor *> list = _editors;
  list.remove(currentEditor());
  TQValueList<Editor *>::iterator it = list.begin();
  bool ok = true;
  for (; it!=list.end(); ++it) {
    if ( !closeEditor(*it, true) ) {
      ok = false;
      break;
    }
  }
  emit guiChanged();
  return ok;
}

bool EditorManager::closeEditor(const PURL::Url &url)
{
  Editor *e = findEditor(url);
  if ( e==0 ) return true;
  if ( !closeEditor(e, true) ) return false;
  emit guiChanged();
  return true;
}

void EditorManager::closeRequest(int i)
{
  closeEditor(static_cast<Editor *>(page(i)), true);
  emit guiChanged();
}

bool EditorManager::closeEditor(Editor *e, bool ask)
{
  if ( e==0 ) return true;
  if ( ask && !e->checkSaved() ) return false;
  removePage(e);
  _editors.remove(e);
  Editor *g = static_cast<Editor *>(currentPage());
  changeToEditor(g);
  saveBookmarks(*e);
  delete e;
  return true;
}

void EditorManager::saveBookmarks(const Editor &e)
{
  if ( Main::project()==0 ) return;
  TQValueList<uint> lines = e.bookmarkLines();
  Main::project()->setBookmarkLines(e.url(), lines);
}

void EditorManager::restoreBookmarks(Editor &e)
{
  if ( Main::project()==0 ) return;
  TQValueList<uint> lines = Main::project()->bookmarkLines(e.url());
  e.setBookmarkLines(lines);
}

void EditorManager::showEditor(Editor *e)
{
  changeToEditor(e);
  emit guiChanged();
}

void EditorManager::changeToEditor(Editor *e)
{
  if ( e==_current ) return;
  connectEditor(e);
  _current = e;
  if (e) {
    showPage(e);
    e->clearFocus();   // force a got focus signal
    e->setFocus();
    e->statusChanged();
    _history.add(name(*e));
  } else {
    emit statusChanged(TQString());
    _history.closedLast();
  }
}

Editor *EditorManager::findEditor(const TQString &tag)
{
  TQValueList<Editor *>::iterator it = _editors.begin();
  for (; it!=_editors.end(); ++it) if ( (*it)->tag()==tag ) return *it;
  return 0;
}

Editor *EditorManager::findEditor(const PURL::Url &url)
{
  TQValueList<Editor *>::iterator it = _editors.begin();
  for (; it!=_editors.end(); ++it) if ( (*it)->url()==url ) return *it;
  return 0;
}

Editor *EditorManager::createEditor(PURL::FileType type, const PURL::Url &url)
{
  Editor *e = findEditor(url);
  if (e) closeEditor(e, false);
  switch (type.type()) {
    case PURL::Hex:
      e = new HexEditor(this);
      break;
    case PURL::CSource:
    case PURL::CppSource:
    case PURL::CHeader:
    case PURL::AsmGPAsm:
    case PURL::AsmPIC30:
    case PURL::AsmPICC:
    case PURL::Inc:
    case PURL::JalSource:
    case PURL::BasicSource:
      e = new TextEditor(true, this);
      break;
    case PURL::Lkr:
    case PURL::Gld:
    case PURL::Map:
    case PURL::Lst:
    case PURL::Cod:
    case PURL::Unknown:
      e = new TextEditor(false, this);
      break;
    case PURL::Coff:
      if ( Main::project()==0 ) {
        if ( Main::deviceData()==0 ) return 0;
        e = new Coff::CoffEditor(url, *Main::deviceData(), this);
      } else {
        if ( Debugger::manager->coff()==0 ) return 0;
        e = new Coff::CoffEditor(*Debugger::manager->coff(), this);
      }
      static_cast<Coff::CoffEditor *>(e)->setView(&Main::compileLog());
      break;
    case PURL::Object:
      e = new Coff::ObjectEditor(url, this);
      static_cast<Coff::ObjectEditor *>(e)->setView(&Main::compileLog());
      break;
    case PURL::Library:
      e = new Coff::LibraryEditor(url, this);
      static_cast<Coff::LibraryEditor *>(e)->setView(&Main::compileLog());
      break;
    case PURL::Elf:
    case PURL::Project:
    case PURL::PikdevProject:
    case PURL::Nb_FileTypes: break;
  }
  return e;
}

void EditorManager::addEditor(Editor *e)
{
  TQValueList<Editor *>::iterator it = _editors.begin();
  for (; it!=_editors.end(); ++it) if ( *it==e ) return;
  _editors.append(e);
  addTab(e, TQString());
  setTabEnabled(e, true);
  restoreBookmarks(*e);
  showEditor(e);
}

void EditorManager::slotDropEvent(TQDropEvent *event)
{
  TQStringList urls;
  if ( !TQUriDrag::decodeLocalFiles(event, urls)) return;
  TQStringList::const_iterator it = urls.begin();
  for(; it!=urls.end(); ++it) openEditor(PURL::Url::fromPathOrUrl(*it));
}

Editor *EditorManager::openEditor(const PURL::Url &url)
{
  Editor *e = findEditor(url);
  if ( e==0 ) {
    e = createEditor(url.fileType(), url);
    if ( e==0 ) return 0;
    if ( !e->open(url) ) {
      closeEditor(e, false);
      return 0;
    }
    addEditor(e);
  } else showEditor(e);
  return e;
}

void EditorManager::saveAllFiles()
{
  TQValueList<Editor *>::iterator it = _editors.begin();
  for (; it!=_editors.end(); ++it) {
    if ( !(*it)->isModified() ) continue;
    (*it)->save();
  }
  emit guiChanged();
}

PURL::UrlList EditorManager::files() const
{
  PURL::UrlList names;
  TQValueList<Editor *>::const_iterator it = _editors.begin();
  for(; it!=_editors.end(); ++it) {
    if ( (*it)->url().isEmpty() ) continue;
    names.push_back((*it)->url());
  }
  return names;
}

void EditorManager::contextMenu(int i, const TQPoint &p)
{
  Editor *editor = static_cast<Editor *>(page(i));
  if ( editor==0 ) return;

  KIconLoader loader;
  TQPixmap closeIcon = loader.loadIcon("fileclose", KIcon::Small);
  TQPixmap saveIcon = loader.loadIcon("filesave", KIcon::Small);
  TQPixmap saveAsIcon = loader.loadIcon("filesaveas", KIcon::Small);
  TQPixmap reloadIcon = loader.loadIcon("reload", KIcon::Small);
  TDEPopupMenu *popup = new TDEPopupMenu;
  popup->insertTitle(title(*editor));
  popup->insertItem(closeIcon, i18n("Close"), 1);
  if ( nbEditors()>1 ) popup->insertItem(i18n("Close All Others"), 2);
  if ( editor->isModified() ) popup->insertItem(saveIcon, i18n("Save"), 3);
  popup->insertItem(saveAsIcon, i18n("Save As..."), 4);
  if ( !editor->url().isEmpty() ) popup->insertItem(reloadIcon, i18n("Reload"), 5);
  switch (popup->exec(p)) {
    case 1: closeEditor(editor, true); break;
    case 2: closeAllOtherEditors(); break;
    case 3: editor->save(); break;
    case 4: editor->saveAs(); break;
    case 5: editor->reload(); break;
  }
  emit guiChanged();
  delete popup;
}

void EditorManager::switchHeaderImplementation()
{
  if ( currentEditor()==0 ) return;
  PURL::Url url = currentEditor()->url();
  PURL::FileType type = url.fileType();
  PURL::SourceFamily source = type.data().sourceFamily;
  if ( type.data().group==PURL::Source ) type = source.data().headerType;
  else {
    Q_ASSERT( type.data().group==PURL::Source );
    type = Main::toolGroup().implementationType(source.data().toolType);
  }
  if ( type==PURL::Nb_FileTypes ) return;
  url = url.toFileType(type);
  if ( !url.exists() ) return;
  openEditor(url);
}

void EditorManager::switchToEditor()
{
  TQStringList names;
  for (uint i=0; i<_editors.count(); i++) names.append(title(*_editors[i]));
  SwitchToDialog dialog(names, this);
  if ( dialog.exec()!=TQDialog::Accepted ) return;
  for (uint i=0; i<names.count(); i++) {
    if ( dialog.name()!=names[i] && dialog.name()!=TQString("%1").arg(i+1) ) continue;
    showEditor(_editors[i]);
    return;
  }
}

TQString EditorManager::name(const Editor &e) const
{
  return (!e.name().isEmpty() ? e.name() : e.url().filepath());
}

void EditorManager::goBack()
{
  Q_ASSERT( _history.hasBack() );
  TQString s = _history.goBack();
  for (uint i=0; i<_editors.count(); i++)
    if ( s==name(*_editors[i]) ) showEditor(_editors[i]);
}

void EditorManager::goForward()
{
  Q_ASSERT( _history.hasForward() );
  TQString s = _history.goForward();
  for (uint i=0; i<_editors.count(); i++)
    if ( s==name(*_editors[i]) ) showEditor(_editors[i]);
}

Editor *EditorManager::openEditor(EditorType type)
{
  bool created = false;
  Editor *e = 0;
  TQString tag = EDITOR_TAGS[type];
  switch (type) {
  case DeviceEditor: {
    e = findEditor(tag);
    if ( e==0 ) {
      e = new DeviceChooser::Editor(i18n("Device"), tag, this);
      static_cast<DeviceChooser::Editor *>(e)->setDevice(false);
      static_cast<DeviceChooser::Editor *>(e)->setDevice(true); // #### needed to fix GUI glitch ???
      created = true;
    }
    break;
  }
  case RegisterEditor: {
    e = findEditor(tag);
    if ( e==0 ) {
      ::PBusyCursor bc;
      e = new Register::MainView(i18n("Registers"), tag);
      static_cast<Debugger::GuiManager *>(Debugger::manager)->addRegisterView(*static_cast<Register::MainView *>(e));
      created = true;
    }
    break;
  }
  case Nb_EditorTypes: Q_ASSERT(false); break;
  }
  if ( e==0 ) return 0;
  if (created) {
    if ( !e->slotLoad() ) {
      delete e;
      return 0;
    }
    addEditor(e);
  } else showEditor(e);
  return e;
}