/*
    This file is part of KOrganizer.

    Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>

    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.

    As a special exception, permission is given to link this program
    with any edition of TQt, and distribute the resulting executable,
    without including the source code for TQt in the source distribution.
*/

#include "incidencechanger.h"
#include "koglobals.h"
#include "koprefs.h"
#include "kogroupware.h"
#include "mailscheduler.h"

#include <libkcal/freebusy.h>
#include <libkcal/dndfactory.h>
#include <kdebug.h>
#include <tdemessagebox.h>
#include <tdelocale.h>

bool IncidenceChanger::beginChange( Incidence *incidence,
                                    ResourceCalendar *res, const TQString &subRes )
{
  if ( !incidence ) {
    return false;
  }

  kdDebug(5850) << "IncidenceChanger::beginChange for incidence \""
                << incidence->summary() << "\"" << endl;

  CalendarResources *calRes = dynamic_cast<CalendarResources*>( mCalendar );
  if ( !calRes ) {
    return false;
  }

  return calRes->beginChange( incidence, res, subRes );
}

bool IncidenceChanger::sendGroupwareMessage( Incidence *incidence,
                                             KCal::Scheduler::Method method,
                                             KOGlobals::HowChanged action,
                                             TQWidget *parent )
{
  if ( KOPrefs::instance()->thatIsMe( incidence->organizer().email() ) && incidence->attendeeCount()>0
      && !KOPrefs::instance()->mUseGroupwareCommunication ) {
    emit schedule( method, incidence );
    return true;
  } else if( KOPrefs::instance()->mUseGroupwareCommunication ) {
    return
      KOGroupware::instance()->sendICalMessage( parent, method, incidence, action, false );
  }
  return true;
}

void IncidenceChanger::cancelAttendees( Incidence *incidence )
{
  if ( KOPrefs::instance()->mUseGroupwareCommunication ) {
    if ( KMessageBox::questionYesNo( 0, i18n("Some attendees were removed "
       "from the incidence. Shall cancel messages be sent to these attendees?"),
       i18n( "Attendees Removed" ), i18n("Send Messages"), i18n("Do Not Send") ) == KMessageBox::Yes ) {
      // don't use KOGroupware::sendICalMessage here, because that asks just
      // a very general question "Other people are involved, send message to
      // them?", which isn't helpful at all in this situation. Afterwards, it
      // would only call the MailScheduler::performTransaction, so do this
      // manually.
      // FIXME: Groupware scheduling should be factored out to it's own class
      //        anyway
      KCal::MailScheduler scheduler( mCalendar );
      scheduler.performTransaction( incidence, Scheduler::Cancel );
    }
  }
}

bool IncidenceChanger::endChange( Incidence *incidence,
                                  ResourceCalendar *res, const TQString &subRes )
{
  // FIXME: if that's a groupware incidence, and I'm not the organizer,
  // send out a mail to the organizer with a counterproposal instead
  // of actually changing the incidence. Then no locking is needed.
  // FIXME: if that's a groupware incidence, and the incidence was
  // never locked, we can't unlock it with endChange().

  if ( !incidence ) {
    return false;
  }

  kdDebug(5850) << "IncidenceChanger::endChange for incidence \""
                << incidence->summary() << "\"" << endl;

  CalendarResources *calRes = dynamic_cast<CalendarResources*>( mCalendar );
  if ( !calRes ) {
    return false;
  }

  return calRes->endChange( incidence, res, subRes );
}

bool IncidenceChanger::deleteIncidence( Incidence *incidence, TQWidget *parent )
{
  if ( !incidence ) return true;
kdDebug(5850)<<"IncidenceChanger::deleteIncidence for incidence \""<<incidence->summary()<<"\""<<endl;
  bool doDelete = sendGroupwareMessage( incidence, KCal::Scheduler::Cancel,
                                        KOGlobals::INCIDENCEDELETED, parent );
  if( doDelete ) {
    // @TODO: let Calendar::deleteIncidence do the locking...
    Incidence* tmp = incidence->clone();
    emit incidenceToBeDeleted( incidence );
    doDelete = mCalendar->deleteIncidence( incidence );
    if ( !KOPrefs::instance()->thatIsMe( tmp->organizer().email() ) ) {
      const TQStringList myEmails = KOPrefs::instance()->allEmails();
      bool notifyOrganizer = false;
      for ( TQStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
        TQString email = *it;
        Attendee *me = tmp->attendeeByMail(email);
        if ( me ) {
          if ( me->status() == KCal::Attendee::Accepted || me->status() == KCal::Attendee::Delegated )
            notifyOrganizer = true;
          Attendee *newMe = new Attendee( *me );
          newMe->setStatus( KCal::Attendee::Declined );
          tmp->clearAttendees();
          tmp->addAttendee( newMe );
          break;
        }
      }

      if ( !KOGroupware::instance()->doNotNotify() && notifyOrganizer ) {
          KCal::MailScheduler scheduler( mCalendar );
          scheduler.performTransaction( tmp, Scheduler::Reply );
      }
      //reset the doNotNotify flag
      KOGroupware::instance()->setDoNotNotify( false );
    }
    emit incidenceDeleted( incidence );
  }
  return doDelete;
}

bool IncidenceChanger::cutIncidences( const Incidence::List &incidences,
                                      TQWidget *parent )
{
  Incidence::List::ConstIterator it;
  bool doDelete = true;
  Incidence::List incsToCut;
  for ( it = incidences.constBegin(); it != incidences.constEnd(); ++it ) {
    if ( *it ) {
      doDelete = sendGroupwareMessage( *it, KCal::Scheduler::Cancel,
                                       KOGlobals::INCIDENCEDELETED, parent );
      if ( doDelete ) {
        emit incidenceToBeDeleted( *it );
        incsToCut.append( *it );
      }
    }
  }

  DndFactory factory( mCalendar );

  if ( factory.cutIncidences( incsToCut ) ) {
    for ( it = incsToCut.constBegin(); it != incsToCut.constEnd(); ++it ) {
      emit incidenceDeleted( *it );
    }
    return !incsToCut.isEmpty();
  } else {
    return false;
  }
}

bool IncidenceChanger::cutIncidence( Incidence *incidence, TQWidget *parent )
{
  Incidence::List incidences;
  incidences.append( incidence );
  return cutIncidences( incidences, parent );
}

class IncidenceChanger::ComparisonVisitor : public IncidenceBase::Visitor
{
  public:
    ComparisonVisitor() {}
    bool act( IncidenceBase *incidence, IncidenceBase *inc2 )
    {
      mIncidence2 = inc2;
      if ( incidence )
        return incidence->accept( *this );
      else
        return (inc2 == 0);
    }
  protected:
    bool visit( Event *event )
    {
      Event *ev2 = dynamic_cast<Event*>(mIncidence2);
      if ( event && ev2 ) {
        return *event == *ev2;
      } else {
        // either both 0, or return false;
        return ( ev2 == event );
      }
    }
    bool visit( Todo *todo )
    {
      Todo *to2 = dynamic_cast<Todo*>( mIncidence2 );
      if ( todo && to2 ) {
        return *todo == *to2;
      } else {
        // either both 0, or return false;
        return ( todo == to2 );
      }
    }
    bool visit( Journal *journal )
    {
      Journal *j2 = dynamic_cast<Journal*>( mIncidence2 );
      if ( journal && j2 ) {
        return *journal == *j2;
      } else {
        // either both 0, or return false;
        return ( journal == j2 );
      }
    }
    bool visit( FreeBusy *fb )
    {
      FreeBusy *fb2 = dynamic_cast<FreeBusy*>( mIncidence2 );
      if ( fb && fb2 ) {
        return *fb == *fb2;
      } else {
        // either both 0, or return false;
        return ( fb2 == fb );
      }
    }

  protected:
    IncidenceBase *mIncidence2;
};

class IncidenceChanger::AssignmentVisitor : public IncidenceBase::Visitor
{
  public:
    AssignmentVisitor() {}
    bool act( IncidenceBase *incidence, IncidenceBase *inc2 )
    {
      mIncidence2 = inc2;
      if ( incidence )
        return incidence->accept( *this );
      else
        return false;
    }
  protected:
    bool visit( Event *event )
    {
      Event *ev2 = dynamic_cast<Event*>( mIncidence2 );
      if ( event && ev2 ) {
        *event = *ev2;
        return true;
      } else {
        return false;
      }
    }
    bool visit( Todo *todo )
    {
      Todo *to2 = dynamic_cast<Todo*>( mIncidence2 );
      if ( todo && to2 ) {
        *todo = *to2;
        return true;
      } else {
        return false;
      }
    }
    bool visit( Journal *journal )
    {
      Journal *j2 = dynamic_cast<Journal*>(mIncidence2);
      if ( journal && j2 ) {
        *journal = *j2;
        return true;
      } else {
        return false;
      }
    }
    bool visit( FreeBusy *fb )
    {
      FreeBusy *fb2 = dynamic_cast<FreeBusy*>( mIncidence2 );
      if ( fb && fb2 ) {
        *fb = *fb2;
        return true;
      } else {
        return false;
      }
    }

  protected:
    IncidenceBase *mIncidence2;
};

bool IncidenceChanger::incidencesEqual( Incidence *inc1, Incidence *inc2 )
{
  ComparisonVisitor v;
  return ( v.act( inc1, inc2 ) );
}

bool IncidenceChanger::assignIncidence( Incidence *inc1, Incidence *inc2 )
{
  AssignmentVisitor v;
  return v.act( inc1, inc2 );
}

bool IncidenceChanger::myAttendeeStatusChanged( Incidence *oldInc, Incidence *newInc )
{
  Attendee *oldMe = oldInc->attendeeByMails( KOPrefs::instance()->allEmails() );
  Attendee *newMe = newInc->attendeeByMails( KOPrefs::instance()->allEmails() );
  if ( oldMe && newMe && ( oldMe->status() != newMe->status() ) )
    return true;

  return false;
}

bool IncidenceChanger::changeIncidence( Incidence *oldinc, Incidence *newinc,
                                        KOGlobals::WhatChanged action,
                                        TQWidget *parent )
{
  return changeIncidence( oldinc, newinc, action, parent, 0 );
}

bool IncidenceChanger::changeIncidence( Incidence *oldinc, Incidence *newinc,
                                        KOGlobals::WhatChanged action,
                                        TQWidget *parent,
                                        int dontAskForGroupware )
{
kdDebug(5850)<<"IncidenceChanger::changeIncidence for incidence \""<<newinc->summary()<<"\" ( old one was \""<<oldinc->summary()<<"\")"<<endl;
  if ( incidencesEqual( newinc, oldinc ) ) {
    // Don't do anything
    kdDebug(5850) << "Incidence not changed\n";
  } else {
    kdDebug(5850) << "Incidence changed\n";
    bool attendeeStatusChanged = myAttendeeStatusChanged( oldinc, newinc );
    int revision = newinc->revision();
    newinc->setRevision( revision + 1 );
    // FIXME: Use a generic method for this! Ideally, have an interface class
    //        for group scheduling. Each implementation could then just do what
    //        it wants with the event. If no groupware is used,use the null
    //        pattern...
    bool success = true;
    if ( KOPrefs::instance()->mUseGroupwareCommunication ) {
      success = KOGroupware::instance()->sendICalMessage(
        parent,
        KCal::Scheduler::Request,
        newinc, KOGlobals::INCIDENCEEDITED, attendeeStatusChanged, dontAskForGroupware );
    }

    if ( success ) {
      // Accept the event changes
      emit incidenceChanged( oldinc, newinc, action );
    } else {
      // revert changes
      assignIncidence( newinc, oldinc );
      return false;
    }
  }
  return true;
}

bool IncidenceChanger::addIncidence( Incidence *incidence,
                                     ResourceCalendar *res, const TQString &subRes,
                                     TQWidget *parent )
{
  return addIncidence( incidence, res, subRes, parent, 0 );
}

bool IncidenceChanger::addIncidence( Incidence *incidence,
                                     ResourceCalendar *res, const TQString &subRes,
                                     TQWidget *parent, int dontAskForGroupware )
{
  CalendarResources *stdcal = dynamic_cast<CalendarResources *>( mCalendar );
  if( stdcal && !stdcal->hasCalendarResources() ) {
    KMessageBox::sorry(
      parent,
      i18n( "No calendars found, unable to save %1 \"%2\"." ).
      arg( i18n( incidence->type() ) ).
      arg( incidence->summary() ) );
    kdDebug(5850) << "IncidenceChanger: No calendars found" << endl;
    return false;
  }

  // FIXME: This is a nasty hack, since we need to set a parent for the
  //        resource selection dialog. However, we don't have any UI methods
  //        in the calendar, only in the CalendarResources::DestinationPolicy
  //        So we need to type-cast it and extract it from the CalendarResources
  TQWidget *tmpparent = 0;
  if ( stdcal ) {
    tmpparent = stdcal->dialogParentWidget();
    stdcal->setDialogParentWidget( parent );
  }

  // If a ResourceCalendar isn't provided, then try to compute one
  // along with any subResource from the incidence.
  ResourceCalendar *pRes = res;
  TQString pSubRes = subRes;
  TQString pResName;
  if ( !pRes ) {
    if ( stdcal ) {
      pRes = stdcal->resource( incidence );
      if ( pRes ) {
        pResName = pRes->resourceName();
        if ( pRes->canHaveSubresources() ) {
          pSubRes = pRes->subresourceIdentifier( incidence );
          pResName = pRes->labelForSubresource( pSubRes );
        }
      }
    }
  }

  bool success = false;
  if ( stdcal && pRes && !pRes->readOnly() && pRes->subresourceWritable( pSubRes ) ) {
    success = stdcal->addIncidence( incidence, pRes, pSubRes );
  } else {
    success = mCalendar->addIncidence( incidence );
  }

  if ( !success ) {
    // We can have a failure if the user pressed [cancel] in the resource
    // selectdialog, so check the exception.
    ErrorFormat *e = stdcal ? stdcal->exception() : 0;
    if ( !e ||
         ( e && ( e->errorCode() != KCal::ErrorFormat::UserCancel &&
                  e->errorCode() != KCal::ErrorFormat::NoWritableFound ) ) ) {
      TQString errMessage;
      if ( pResName.isEmpty() ) {
        errMessage = i18n( "Unable to save %1 \"%2\"." ).
                     arg( i18n( incidence->type() ) ).
                     arg( incidence->summary() );
      } else {
        errMessage = i18n( "Unable to save %1 \"%2\" to calendar %3." ).
                     arg( i18n( incidence->type() ) ).
                     arg( incidence->summary() ).
                     arg( pResName );
      }
      KMessageBox::sorry( parent, errMessage );
    }
    kdDebug(5850) << "IncidenceChanger: Can't add incidence" << endl;
    return false;
  }

  if ( KOPrefs::instance()->mUseGroupwareCommunication ) {
    if ( !KOGroupware::instance()->sendICalMessage(
           parent,
           KCal::Scheduler::Request,
           incidence, KOGlobals::INCIDENCEADDED, false, dontAskForGroupware ) ) {
      KMessageBox::sorry(
        parent,
        i18n( "Attempt to send the scheduling message failed. "
              "Please check your Group Scheduling settings. "
              "Contact your system administrator for more help.") );
    }
  }

  emit incidenceAdded( incidence );
  return true;
}


#include "incidencechanger.moc"
#include "incidencechangerbase.moc"
