// 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 "label.h"
#include "normal.h"

#include "textlabelwizard.h"
#include "linkslabel.h"

#include "../kig/kig_commands.h"
#include "../kig/kig_document.h"
#include "../kig/kig_part.h"
#include "../kig/kig_view.h"
#include "../misc/common.h"
#include "../misc/kigpainter.h"
#include "../objects/bogus_imp.h"
#include "../objects/curve_imp.h"
#include "../objects/object_factory.h"
#include "../objects/point_imp.h"
#include "../objects/text_imp.h"
#include "../objects/text_type.h"

#include <kcursor.h>
#include <tdemessagebox.h>
#include <tdeaction.h>
#include <tdelocale.h>
#include <tqtextedit.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <tqregexp.h>
#include <tqpopupmenu.h>
#include <tqcheckbox.h>

#include <algorithm>
#include <functional>

class TextLabelModeBase::Private
{
public:
  // point last clicked..
  TQPoint plc;
  // the currently selected coordinate
  Coordinate mcoord;
  // the possible parent object that defines the location of the label..
  ObjectCalcer* locationparent;

  // the text is only kept in the text input widget, not here
  // TQString mtext;

  // the property objects we'll be using as args, we keep a reference
  // to them in the args object, and keep a pointer to them ( or 0 )
  // in the correct order in args ( separately, because we can't use
  // the order of the parents of a ReferenceObject, and certainly
  // can't give 0 as a parent..
  argvect args;

  // if we're ReallySelectingArgs, then this var points to the arg
  // we're currently selecting...
  int mwaaws;

  // last percent count...
  uint lpc;

  TextLabelWizard* wiz;

  // What Are We Doing
  wawdtype mwawd;
};

TextLabelModeBase::~TextLabelModeBase()
{
  delete d->wiz;
  delete d;
}

TextLabelModeBase::TextLabelModeBase( KigPart& doc )
  : KigMode( doc ), d( new Private )
{
  d->locationparent = 0;
  d->lpc = 0;
  d->mwawd = SelectingLocation;
  d->wiz = new TextLabelWizard( doc.widget(), this );
}

void TextLabelModeBase::leftClicked( TQMouseEvent* e, KigWidget* )
{
  d->plc = e->pos();
  switch( d->mwawd )
  {
  case RequestingText:
  case SelectingArgs:
    d->wiz->raise();
    d->wiz->setActiveWindow();
    break;
  default:
    break;
  };
}

void TextLabelModeBase::leftReleased( TQMouseEvent* e, KigWidget* v )
{
  switch( d->mwawd )
  {
  case SelectingLocation:
  {
    if ( ( d->plc - e->pos() ).manhattanLength() > 4 ) return;
    setCoordinate( v->fromScreen( d->plc ) );
    break;
  }
  case RequestingText:
  case SelectingArgs:
    d->wiz->raise();
    d->wiz->setActiveWindow();
    break;
  case ReallySelectingArgs:
  {
    if ( ( d->plc - e->pos() ).manhattanLength() > 4 ) break;
    std::vector<ObjectHolder*> os = mdoc.document().whatAmIOn( v->fromScreen( d->plc ), *v );
    if ( os.empty() ) break;
    ObjectHolder* o = os[0];
    TQPopupMenu* p = new TQPopupMenu( v, "text_label_select_arg_popup" );
    p->insertItem( i18n( "Name" ), 0 );
    QCStringList l = o->imp()->properties();
    assert( l.size() == o->imp()->numberOfProperties() );
    for ( int i = 0; static_cast<uint>( i ) < l.size(); ++i )
    {
      TQString s = i18n( l[i] );
      const char* iconfile = o->imp()->iconForProperty( i );
      int t;
      if ( iconfile && *iconfile )
      {
        TQPixmap pix = mdoc.instance()->iconLoader()->loadIcon( iconfile, TDEIcon::User );
        t = p->insertItem( TQIconSet( pix ), s, i + 1 );
      }
      else
      {
        t = p->insertItem( s, i + 1 );
      };
      assert( t == i + 1 );
    };
    int result = p->exec( v->mapToGlobal( d->plc ) );
    ObjectCalcer::shared_ptr argcalcer;
    if ( result == -1 ) break;
    else if ( result == 0 )
    {
      argcalcer = o->nameCalcer();
      if ( !argcalcer )
      {
        ObjectConstCalcer* c = new ObjectConstCalcer( new StringImp( i18n( "<unnamed object>" ) ) );
        o->setNameCalcer( c );
        argcalcer = c;
      }
    }
    else
    {
      assert( static_cast<uint>( result ) < l.size() + 1 );
      argcalcer = new ObjectPropertyCalcer( o->calcer(), result - 1 );
    }
    d->args[d->mwaaws] = argcalcer.get();
    argcalcer->calc( mdoc.document() );

    updateLinksLabel();
    updateWiz();
    break;
  }
  default:
    assert( false );
    break;
  };
}

void TextLabelModeBase::killMode()
{
  mdoc.doneMode( this );
}

void TextLabelModeBase::cancelConstruction()
{
  killMode();
}

void TextLabelModeBase::enableActions()
{
  KigMode::enableActions();

  mdoc.aCancelConstruction->setEnabled( true );
}

void TextLabelModeBase::mouseMoved( TQMouseEvent* e, KigWidget* w )
{
  if ( d->mwawd == ReallySelectingArgs )
  {
    std::vector<ObjectHolder*> os = mdoc.document().whatAmIOn( w->fromScreen( e->pos() ), *w );
    if ( !os.empty() ) w->setCursor( KCursor::handCursor() );
    else w->setCursor( KCursor::arrowCursor() );
  }
  else if ( d->mwawd == SelectingLocation )
  {
    std::vector<ObjectHolder*> os = mdoc.document().whatAmIOn( w->fromScreen( e->pos() ), *w );
    bool attachable = false;
    d->locationparent = 0;
    for ( std::vector<ObjectHolder*>::iterator i = os.begin(); i != os.end(); ++i )
    {
      if( (*i)->imp()->attachPoint().valid() ||
          (*i)->imp()->inherits( PointImp::stype() ) ||
          (*i)->imp()->inherits( CurveImp::stype() ) )
      {
        attachable = true;
        d->locationparent = (*i)->calcer();
        break;
      };
    };
    w->updateCurPix();
    if ( attachable )
    {
      w->setCursor( KCursor::handCursor() );
      TQString s = d->locationparent->imp()->type()->attachToThisStatement();
      mdoc.emitStatusBarText( s );

      KigPainter p( w->screenInfo(), &w->curPix, mdoc.document() );

      // set the text next to the arrow cursor
      TQPoint point = e->pos();
      point.setX(point.x()+15);

      p.drawTextStd( point, s );
      w->updateWidget( p.overlay() );
    }
    else
    {
      w->setCursor( KCursor::crossCursor() );
      mdoc.emitStatusBarText( 0 );
      w->updateWidget();
    };
  }
}

void TextLabelModeBase::enterTextPageEntered()
{
}

void TextLabelModeBase::selectArgumentsPageEntered()
{
  updateLinksLabel();
}

void TextLabelModeBase::cancelPressed()
{
  cancelConstruction();
}

static uint percentCount( const TQString& s )
{
//  TQRegExp re( TQString::fromUtf8( "%[0-9]" ) );
  TQRegExp re( TQString::fromUtf8( "%[\\d]+" ) );
  int offset = 0;
  uint percentcount = 0;
  while ( ( offset = re.search( s, offset ) ) != -1 )
  {
    ++percentcount;
    offset += re.matchedLength();
  };
  return percentcount;
}

void TextLabelModeBase::finishPressed()
{
  bool needframe = d->wiz->needFrameCheckBox->isChecked();
  TQString s = d->wiz->labelTextInput->text();

  assert( percentCount( s ) == d->args.size() );
  if ( d->wiz->currentPage() == d->wiz->enter_text_page )
    assert( d->args.size() == 0 );

  bool finished = true;
  for ( argvect::iterator i = d->args.begin(); i != d->args.end(); ++i )
    finished &= ( *i != 0 );

  if ( ! finished )
    KMessageBox::sorry( mdoc.widget(),
                        i18n( "There are '%n' parts in the text that you have not selected a "
                              "value for. Please remove them or select enough arguments." ) );
  else
  {
    finish( d->mcoord, s, d->args, needframe, d->locationparent );
    killMode();
  };
}

void TextLabelModeBase::updateWiz()
{
  TQString s = d->wiz->labelTextInput->text();
  uint percentcount = percentCount( s );
  if ( d->lpc > percentcount )
  {
    d->args = argvect( d->args.begin(), d->args.begin() + percentcount );
  }
  else if ( d->lpc < percentcount )
  {
    d->args.resize( percentcount, 0 );
  };

  if ( percentcount == 0 && ! s.isEmpty() )
  {
    d->wiz->setNextEnabled( d->wiz->enter_text_page, false );
    d->wiz->setFinishEnabled( d->wiz->enter_text_page, true );
    d->wiz->setAppropriate( d->wiz->select_arguments_page, false );
  }
  else
  {
    d->wiz->setAppropriate( d->wiz->select_arguments_page, !s.isEmpty() );
    d->wiz->setNextEnabled( d->wiz->enter_text_page, ! s.isEmpty() );
    d->wiz->setFinishEnabled( d->wiz->enter_text_page, false );
    bool finished = true;
    for ( argvect::iterator i = d->args.begin(); i != d->args.end(); ++i )
      finished &= ( *i != 0 );
    assert( percentCount( s ) == d->args.size() );

    d->wiz->setFinishEnabled( d->wiz->select_arguments_page, finished );
  };

  d->lpc = percentcount;
}

void TextLabelModeBase::labelTextChanged()
{
  updateWiz();
}

void TextLabelModeBase::updateLinksLabel()
{
  LinksLabel::LinksLabelEditBuf buf = d->wiz->myCustomWidget1->startEdit();
  TQString s = d->wiz->labelTextInput->text();
//  TQRegExp re( "%[0-9]" );
  TQRegExp re( "%[\\d]+" );
  int prevpos = 0;
  int pos = 0;
  uint count = 0;
  // we split up the string into text and "links"
  while ( ( pos = re.search( s, pos ) ) != -1 )
  {
    // prevpos is the first character after the last match, pos is the
    // first char of the current match..
    if ( prevpos != pos )
    {
      // there is a text part between the previous and the current
      // "link"...
      assert( prevpos < pos );
      // fetch the text part...
      TQString subs = s.mid( prevpos, pos - prevpos );
      // and add it...
      d->wiz->myCustomWidget1->addText( subs, buf );
    };
    // we always need a link part...
    TQString linktext( "%1" );
    assert( count < d->args.size() );
    if ( d->args[count] )
    {
      // if the user has already selected a property, then we show its
      // value...
      d->args[count]->imp()->fillInNextEscape( linktext, mdoc.document() );
    }
    else
      // otherwise, we show a stub...
      linktext = i18n( "argument %1" ).arg( count + 1 );

    d->wiz->myCustomWidget1->addLink( linktext, buf );
    // set pos and prevpos to the next char after the last match, so
    // we don't enter infinite loops...
//    pos += 2;
    pos += re.matchedLength();
    prevpos = pos;
    ++count;
  };

  if ( static_cast<uint>( prevpos ) != s.length() )
    d->wiz->myCustomWidget1->addText( s.mid( prevpos ), buf );

  d->wiz->myCustomWidget1->applyEdit( buf );
  d->wiz->relayoutArgsPage();

  d->wiz->resize( d->wiz->size() );
}

void TextLabelModeBase::linkClicked( int i )
{
  mdoc.widget()->setActiveWindow();
  mdoc.widget()->raise();

  assert( d->args.size() >= static_cast<uint>( i + 1 ) );

  d->mwawd = ReallySelectingArgs;
  d->mwaaws = i;

  mdoc.emitStatusBarText( i18n( "Selecting argument %1" ).arg( i + 1 ) );
}

void TextLabelModeBase::redrawScreen( KigWidget* w )
{
  w->redrawScreen( std::vector<ObjectHolder*>() );
  w->updateScrollBars();
}

void TextLabelModeBase::setCoordinate( const Coordinate& coord )
{
  d->mcoord = coord;
  if ( d->mwawd == SelectingLocation )
  {
    d->mwawd = RequestingText;
    updateWiz();
    d->wiz->show();
    // shouldn't be necessary, but seems to be anyway.. :(
    updateWiz();
  };
}

void TextLabelModeBase::setText( const TQString& s )
{
  d->wiz->labelTextInput->setText( s );
}

void TextLabelModeBase::setPropertyObjects( const argvect& props )
{
  d->args = props;
  for ( argvect::iterator i = d->args.begin(); i != d->args.end(); ++i )
    (*i)->calc( mdoc.document() );
}

TextLabelConstructionMode::TextLabelConstructionMode( KigPart& d )
  : TextLabelModeBase( d )
{
}

TextLabelConstructionMode::~TextLabelConstructionMode()
{
}

void TextLabelConstructionMode::finish(
  const Coordinate& coord, const TQString& s,
  const argvect& props, bool needframe,
  ObjectCalcer* locationparent )
{
  std::vector<ObjectCalcer*> args;
  for ( argvect::const_iterator i = props.begin();
        i != props.end(); ++i )
    args.push_back( i->get() );

  ObjectHolder* label = 0;
  if ( locationparent )
    label = ObjectFactory::instance()->attachedLabel( s, locationparent, coord, needframe, args, mdoc.document() );
  else
    label = ObjectFactory::instance()->label( s, coord, needframe, args, mdoc.document() );
  mdoc.addObject( label );
}

TextLabelRedefineMode::TextLabelRedefineMode( KigPart& d, ObjectTypeCalcer* label )
  : TextLabelModeBase( d ), mlabel( label )
{
  assert( label->imp()->inherits( TextImp::stype() ) );
  std::vector<ObjectCalcer*> parents = label->parents();
  assert( parents.size() >= 3 );
  std::vector<ObjectCalcer*> firstthree( parents.begin(), parents.begin() + 3 );
  std::vector<ObjectCalcer*> rest( parents.begin() + 3, parents.end() );
  firstthree = TextType::instance()->argParser().parse( firstthree );

  assert( firstthree[0]->imp()->inherits( IntImp::stype() ) );
  assert( firstthree[1]->imp()->inherits( PointImp::stype() ) );
  assert( firstthree[2]->imp()->inherits( StringImp::stype() ) );

  bool frame = static_cast<const IntImp*>( firstthree[0]->imp() )->data() != 0;
  Coordinate coord = static_cast<const PointImp*>( firstthree[1]->imp() )->coordinate();
  TQString text = static_cast<const StringImp*>( firstthree[2]->imp() )->data();

  // don't set it, let the user redefine it..
//  setCoordinate( coord );
  setText( text );
  setFrame( frame );

  argvect v;
  for ( uint i = 0; i < rest.size(); ++i )
  {
    v.push_back( rest[i] );
  };
  assert( v.size() == rest.size() );

  setPropertyObjects( v );
}

TextLabelRedefineMode::~TextLabelRedefineMode()
{
}

void TextLabelRedefineMode::finish(
  const Coordinate& coord, const TQString& s,
  const argvect& props, bool needframe,
  ObjectCalcer* locationparent )
{
  std::vector<ObjectCalcer*> parents = mlabel->parents();
  assert( parents.size() >= 3 );
  std::vector<ObjectCalcer*> firstthree( parents.begin(), parents.begin() + 3 );
  std::vector<ObjectCalcer*> rest( parents.begin() + 3, parents.end() );
  firstthree = TextType::instance()->argParser().parse( firstthree );

  KigCommand* kc = new KigCommand( mdoc, i18n( "Change Label" ) );
  MonitorDataObjects mon( firstthree );

  assert( firstthree[0]->imp()->inherits( IntImp::stype() ) );
  assert( firstthree[1]->imp()->inherits( PointImp::stype() ) );
  assert( firstthree[2]->imp()->inherits( StringImp::stype() ) );

  assert( dynamic_cast<ObjectConstCalcer*>( firstthree[0] ) );
  assert( dynamic_cast<ObjectConstCalcer*>( firstthree[2] ) );
  static_cast<ObjectConstCalcer*>( firstthree[0] )->setImp( new IntImp( needframe ? 1 : 0 ) );

  // we don't do this, because
  // 1 this isn't necessarily a DataObject, we also support it to be a
  //   user-known point, or an internal constrained point..
  // 2 we don't know that we don't want it to become a user-known
  //   point or an internal constrained point, instead of a
  //   DataObject..
  // static_cast<DataObject*>( firstthree[1] )->setImp( new PointImp(
  // coord ) );

  static_cast<ObjectConstCalcer*>( firstthree[2] )->setImp( new StringImp( s ) );
  mon.finish( kc );

  std::vector<ObjectCalcer*> oldparents = mlabel->parents();
  std::vector<ObjectCalcer*> p;
  for ( argvect::const_iterator i = props.begin();
        i != props.end(); ++i )
    p.push_back( i->get() );
  for ( std::vector<ObjectCalcer*>::iterator i = p.begin();
        i != p.end(); ++i )
    ( *i )->calc( mdoc.document() );

  std::vector<ObjectCalcer*> np = firstthree;
  /*
   * take advantage of the method "getAttachPoint" that should
   * do all the work; it is also used when creating a new label
   */
  np[1] = ObjectFactory::instance()->getAttachPoint( locationparent, coord, mdoc.document() );

/* this is the old code, just in case... */
//  if ( locationparent && locationparent->imp()->inherits( CurveImp::stype() ) )
//  {
//    double param = static_cast<const CurveImp*>( locationparent->imp() )->getParam( coord, mdoc.document() );
//    np[1] = ObjectFactory::instance()->constrainedPointCalcer( locationparent, param );
//    np[1]->calc( mdoc.document() );
//  }
//  else if ( locationparent )
//  {
//    assert( locationparent->imp()->inherits( PointImp::stype() ) );
//    np[1] = locationparent;
//  }
//  else
//    np[1] = new ObjectConstCalcer( new PointImp( coord ) );

  copy( p.begin(), p.end(), back_inserter( np ) );

  kc->addTask(
    new ChangeParentsAndTypeTask(
      mlabel, np, TextType::instance() ) );

  mdoc.history()->addCommand( kc );
}

void TextLabelModeBase::setFrame( bool f )
{
  d->wiz->needFrameCheckBox->setChecked( f );
}

void TextLabelModeBase::setLocationParent( ObjectCalcer* o )
{
  d->locationparent = o;
}
