/**
   This file is part of Kig, a KDE program for Interactive Geometry...
   Copyright (C) 2002  Dominique Devriese <devriese@kde.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 "typesdialog.h"
#include "typesdialog.moc"

#include "edittype.h"
#include "ui_typeswidget.h"
#include "../kig/kig_part.h"
#include "../misc/guiaction.h"
#include "../misc/object_constructor.h"

#include <kfiledialog.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kpushbutton.h>
#include <ktoolinvocation.h>

#include <qbytearray.h>
#include <qevent.h>
#include <qfile.h>
#include <qlayout.h>
#include <qmenu.h>
#include <qstringlist.h>

#include <algorithm>

static QString wrapAt( const QString& str, int col = 50 )
{
  QStringList ret;
  int delta = 0;
  while ( delta + col < str.length() )
  {
    int pos = delta + col;
    while ( !str.at( pos ).isSpace() ) --pos;
    ret << str.mid( delta, pos - delta );
    delta = pos + 1;
  }
  ret << str.mid( delta );
  return ret.join( "<br>" );
}

class BaseListElement
{
protected:
  BaseListElement();

public:
  virtual ~BaseListElement();

  virtual bool isMacro() const { return false; }
  virtual QString name() const = 0;
  virtual QString description() const = 0;
  virtual QString icon( bool canNull = false ) const = 0;
  virtual QString type() const = 0;
};

BaseListElement::BaseListElement()
{
}

BaseListElement::~BaseListElement()
{
}

class MacroListElement
  : public BaseListElement
{
  Macro* mmacro;
public:
  MacroListElement( Macro* m );
  virtual ~MacroListElement();

  Macro* getMacro() const { return mmacro; }
  virtual bool isMacro() const { return true; }
  virtual QString name() const;
  virtual QString description() const;
  virtual QString icon( bool canNull = false ) const;
  virtual QString type() const;
};

MacroListElement::MacroListElement( Macro* m )
  : BaseListElement(), mmacro( m )
{
}

MacroListElement::~MacroListElement()
{
  // do NOT delete the associated macro, but instead safely release the
  // pointer, as it's kept elsewhere
  mmacro = 0;
}

QString MacroListElement::name() const
{
  return mmacro->action->descriptiveName();
}

QString MacroListElement::description() const
{
  return mmacro->action->description();
}

QString MacroListElement::icon( bool canNull ) const
{
  return mmacro->ctor->iconFileName( canNull );
}

QString MacroListElement::type() const
{
  return i18n( "Macro" );
}


TypesModel::TypesModel( QObject* parent )
  : QAbstractTableModel( parent )
{
}

TypesModel::~TypesModel()
{
}

void TypesModel::addMacros( const std::vector<Macro*>& macros )
{
  if ( macros.size() < 1 )
    return;

  beginInsertRows( QModelIndex(), melems.size(), melems.size() + macros.size() - 1 );

  for ( std::vector<Macro*>::const_iterator it = macros.begin();
        it != macros.end(); ++it )
  {
    melems.push_back( new MacroListElement( *it ) );
  }

  endInsertRows();
}

void TypesModel::removeElements( const QModelIndexList& elems )
{
  // this way of deleting needs some explanation: the std::vector.erase needs
  // an iterator to the element to remove from the list, while the
  // beginRemoveRows() of Qt needs the index(es). so, for each element to
  // delete, we search it into melems (keeping a count of the id), and when we
  // find it, we free the memory of the BaseListElement and remove the element
  // from the list. in the meanwhile, we notify the model structure of Qt that
  // we're removing a row.
  for ( int i = elems.count(); i > 0; --i )
  {
    QModelIndex index = elems.at( i - 1 );
    if ( !index.isValid() || index.row() < 0 || index.row() >= static_cast<int>( melems.size() )
         || index.column() < 0 || index.column() > 3 )
      continue;

    BaseListElement* element = melems[ index.row() ];

    bool found = false;
    int id = 0;
    for ( std::vector<BaseListElement*>::iterator mit = melems.begin();
          mit != melems.end() && !found; )
    {
      if ( *mit == element )
      {
        found = true;
        beginRemoveRows( QModelIndex(), id, id );

        delete (*mit);
        mit = melems.erase( mit );
        element = 0;

        endRemoveRows();
      }
      else
        ++mit;
      ++id;
    }
  }
}

void TypesModel::clear()
{
  for ( std::vector<BaseListElement*>::const_iterator it = melems.begin();
          it != melems.end(); ++it )
    delete *it;
  melems.clear();
}

void TypesModel::elementChanged( const QModelIndex& index )
{
  if ( !index.isValid() || index.row() < 0 || index.row() >= static_cast<int>( melems.size() )
       || index.column() < 0 || index.column() > 3 )
    return;

  QModelIndex left = createIndex( index.row(), 1 );
  QModelIndex right = createIndex( index.row(), 2 );
  emit dataChanged( left, right );
}

bool TypesModel::isMacro( const QModelIndex& index ) const
{
  if ( !index.isValid() || index.row() < 0 || index.row() >= static_cast<int>( melems.size() ) )
    return false;

  return melems[ index.row() ]->isMacro();
}

Macro* TypesModel::macroFromIndex( const QModelIndex& index ) const
{
  if ( !index.isValid() || index.row() < 0 || index.row() >= static_cast<int>( melems.size() ) )
    return 0;

  BaseListElement* el = melems[ index.row() ];
  if ( !el->isMacro() )
    return 0;

  return static_cast<MacroListElement*>( el )->getMacro();
}

int TypesModel::columnCount( const QModelIndex& parent ) const
{
  return parent.isValid() ? 0 : 3;
}

QVariant TypesModel::data( const QModelIndex& index, int role ) const
{
  if ( !index.isValid() )
    return QVariant();

  if ( ( index.row() < 0 ) || ( index.row() >= static_cast<int>( melems.size() ) ) )
    return QVariant();

  switch ( role )
  {
    case Qt::DecorationRole:
    {
      if ( index.column() == 1 )
        return KIcon( melems[ index.row() ]->icon() );
      else
        return QVariant();
      break;
    }
    case Qt::DisplayRole:
    {
      switch ( index.column() )
      {
        case 0: return melems[ index.row() ]->type(); break;
        case 1: return melems[ index.row() ]->name(); break;
        case 2: return melems[ index.row() ]->description(); break;
        default:
          return QVariant();
      }
      break;
    }
    case Qt::ToolTipRole:
    {
      static QString macro_with_image(
          "<qt><table cellspacing=\"5\"><tr><td><b>%1</b> (%4)</td>"
          "<td rowspan=\"2\" align=\"right\"><img src=\"%3\"></td></tr>"
          "<tr><td>%2</td></tr></table></qt>" );
      static QString macro_no_image(
          "<qt><b>%1</b> (%3)<br>%2</qt>" );

      if ( melems[ index.row() ]->icon( true ).isEmpty() )
        return macro_no_image
                 .arg( melems[ index.row() ]->name() )
                 .arg( wrapAt( melems[ index.row() ]->description() ) )
                 .arg( melems[ index.row() ]->type() );
      else
        return macro_with_image
                 .arg( melems[ index.row() ]->name() )
                 .arg( wrapAt( melems[ index.row() ]->description() ) )
                 .arg( KIconLoader::global()->iconPath( melems[ index.row() ]->icon(), - KIconLoader::SizeMedium ) )
                 .arg( melems[ index.row() ]->type() );
    }
    default:
      return QVariant();
  }
}

QVariant TypesModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
  if ( orientation != Qt::Horizontal )
    return QVariant();

  if ( role == Qt::TextAlignmentRole )
    return QVariant( Qt::AlignLeft );

  if ( role != Qt::DisplayRole )
    return QVariant();

  switch ( section )
  {
    case 0: return i18n( "Type" ); break;
    case 1: return i18n( "Name" ); break;
    case 2: return i18n( "Description" ); break;
    default:
      return QVariant();
  }
}

QModelIndex TypesModel::index( int row, int column, const QModelIndex& parent ) const
{
  if ( parent.isValid() || row < 0 || row >= static_cast<int>( melems.size() ) || column < 0 || column > 3 )
    return QModelIndex();

  return createIndex( row, column, melems[row] );
}

int TypesModel::rowCount( const QModelIndex& parent ) const
{
  return parent.isValid() ? 0 : melems.size();
}


TypesDialog::TypesDialog( QWidget* parent, KigPart& part )
  : KDialog( parent ),
    mpart( part )
{
  setCaption( i18n( "Manage Types" ) );
  setButtons( Help | Ok | Cancel );

  QWidget* base = new QWidget( this );
  setMainWidget( base );
  mtypeswidget = new Ui_TypesWidget();
  mtypeswidget->setupUi( base );
  base->layout()->setMargin( 0 );

  // model creation and usage
  mmodel = new TypesModel( mtypeswidget->typeList );
  mtypeswidget->typeList->setModel( mmodel );

  mtypeswidget->typeList->setContextMenuPolicy( Qt::CustomContextMenu );

  // improving GUI look'n'feel...
  mtypeswidget->buttonEdit->setIcon( KIcon( "document-properties" ) );
  mtypeswidget->buttonRemove->setIcon( KIcon( "edit-delete" ) );
  mtypeswidget->buttonExport->setIcon( KIcon( "document-export" ) );
  mtypeswidget->buttonImport->setIcon( KIcon( "document-import" ) );

  // loading macros...
  mmodel->addMacros( MacroList::instance()->macros() );

//  mtypeswidget->typeList->sortItems( 2, Qt::AscendingOrder );

  mtypeswidget->typeList->resizeColumnToContents( 0 );

  popup = new QMenu( this );
  popup->addAction( KIcon( "document-properties" ), i18n( "&Edit..." ), this, SLOT( editType() ) );
  popup->addAction( KIcon( "edit-delete" ), i18n( "&Delete" ), this, SLOT( deleteType() ) );
  popup->addSeparator();
  popup->addAction( KIcon( "document-export" ), i18n( "E&xport..." ), this, SLOT( exportType() ) );

  // saving types
  mpart.saveTypes();

  connect( mtypeswidget->buttonExport, SIGNAL( clicked() ), this, SLOT( exportType() ) );
  connect( mtypeswidget->buttonImport, SIGNAL( clicked() ), this, SLOT( importTypes() ) );
  connect( mtypeswidget->buttonRemove, SIGNAL( clicked() ), this, SLOT( deleteType() ) );
  connect( mtypeswidget->buttonEdit, SIGNAL( clicked() ), this, SLOT( editType() ) );
  connect( mtypeswidget->typeList, SIGNAL( customContextMenuRequested( const QPoint& ) ), this, SLOT( typeListContextMenu( const QPoint& ) ) );
  connect( this, SIGNAL( helpClicked() ), this, SLOT( slotHelp() ) );
  connect( this, SIGNAL( okClicked() ), this, SLOT( slotOk() ) );
  connect( this, SIGNAL( cancelClicked() ), this, SLOT( slotCancel() ) );

  resize( 460, 270 );
}

TypesDialog::~TypesDialog()
{
  delete mtypeswidget;
}

void TypesDialog::slotHelp()
{
  KToolInvocation::invokeHelp( "working-with-types", "kig" );
}

void TypesDialog::slotOk()
{
  mpart.saveTypes();
  mpart.deleteTypes();
  mpart.loadTypes();
  accept();
}

void TypesDialog::deleteType()
{
  std::vector<Macro*> selectedTypes;
  QModelIndexList indexes = selectedRows();
  for ( QModelIndexList::const_iterator it = indexes.constBegin(); it != indexes.constEnd(); ++it )
  {
    Macro* macro = mmodel->macroFromIndex( *it );
    if ( macro )
    {
      selectedTypes.push_back( macro );
    }
  }

  if (selectedTypes.empty()) return;
  QStringList types;
  for ( std::vector<Macro*>::iterator j = selectedTypes.begin();
        j != selectedTypes.end(); ++j )
    types << ( *j )->action->descriptiveName();
  types.sort();
  if ( KMessageBox::warningContinueCancelList( this,
        i18np( "Are you sure you want to delete this type?",
              "Are you sure you want to delete these %1 types?", selectedTypes.size() ),
        types, i18n("Are You Sure?"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
        "deleteTypeWarning") == KMessageBox::Cancel )
     return;
  bool updates = mtypeswidget->typeList->updatesEnabled();
  mtypeswidget->typeList->setUpdatesEnabled( false );
  mmodel->removeElements( indexes );
  mtypeswidget->typeList->setUpdatesEnabled( updates );
  for ( std::vector<Macro*>::iterator j = selectedTypes.begin();
        j != selectedTypes.end(); ++j)
    MacroList::instance()->remove( *j );
}

void TypesDialog::exportType()
{
  std::vector<Macro*> types;
  QModelIndexList indexes = selectedRows();
  for ( QModelIndexList::const_iterator it = indexes.constBegin(); it != indexes.constEnd(); ++it )
  {
    Macro* macro = mmodel->macroFromIndex( *it );
    if ( macro )
      types.push_back( macro );
  }
  if (types.empty()) return;
  QString file_name = KFileDialog::getSaveFileName( KUrl( "kfiledialog:///macro" ), i18n("*.kigt|Kig Types Files\n*|All Files"), this, i18n( "Export Types" ) );
  if ( file_name.isNull() )
    return;
  QFile fi( file_name );
  if ( fi.exists() )
    if ( KMessageBox::warningContinueCancel( this, i18n( "The file \"%1\" already exists. "
                                       "Do you wish to overwrite it?", fi.fileName() ),
                                       i18n( "Overwrite File?" ), KStandardGuiItem::overwrite() ) == KMessageBox::Cancel )
       return;
  MacroList::instance()->save( types, file_name );
}

void TypesDialog::importTypes()
{
  QStringList file_names =
    KFileDialog::getOpenFileNames( KUrl( "kfiledialog:///importTypes" ), i18n("*.kigt|Kig Types Files\n*|All Files"), this, i18n( "Import Types" ));

  std::vector<Macro*> macros;

  for ( QStringList::const_iterator i = file_names.constBegin();
        i != file_names.constEnd(); ++i)
  {
    std::vector<Macro*> nmacros;
    bool ok = MacroList::instance()->load( *i, nmacros, mpart );
    if ( ! ok )
      continue;
    std::copy( nmacros.begin(), nmacros.end(), std::back_inserter( macros ) );
  };
  MacroList::instance()->add( macros );

  mmodel->addMacros( macros );

  mtypeswidget->typeList->resizeColumnToContents( 0 );
}

void TypesDialog::editType()
{
  QModelIndexList indexes = selectedRows();
  if ( indexes.isEmpty() )
    return;
  if ( indexes.count() > 1 )
  {
    KMessageBox::sorry( this,
                        i18n( "There is more than one type selected. You can "
                              "only edit one type at a time. Please select "
                              "only the type you want to edit and try again." ),
                        i18n( "More Than One Type Selected" ) );
    return;
  }
  bool refresh = false;
  QModelIndex index = indexes.first();
  if ( mmodel->isMacro( index ) )
  {
    Macro* oldmacro = mmodel->macroFromIndex( index );
    EditType editdialog( this, oldmacro->action->descriptiveName(), oldmacro->action->description(),
                         oldmacro->ctor->iconFileName( false ) );
    if ( editdialog.exec() )
    {
      QString newname = editdialog.name();
      QString newdesc = editdialog.description();
      QString newicon = editdialog.icon();

//      mpart.unplugActionLists();
      oldmacro->ctor->setName( newname );
      oldmacro->ctor->setDescription( newdesc );
      QByteArray ncicon( newicon.toUtf8() );
      oldmacro->ctor->setIcon( ncicon );
//      mpart.plugActionLists();
      refresh = true;
    }
  }
  if ( refresh )
  {
    mmodel->elementChanged( index );
  }
}

void TypesDialog::slotCancel()
{
  mpart.deleteTypes();
  mpart.loadTypes();
  reject();
}

QModelIndexList TypesDialog::selectedRows() const
{
  QModelIndexList indexes = mtypeswidget->typeList->selectionModel()->selectedRows();
  qSort( indexes );
  return indexes;
}

void TypesDialog::typeListContextMenu( const QPoint& pos )
{
  QModelIndexList indexes = mtypeswidget->typeList->selectionModel()->selectedRows();
  if ( indexes.isEmpty() )
    return;

  popup->exec( mtypeswidget->typeList->viewport()->mapToGlobal( pos ) );
}
