/*
 *   This file only:
 *     Copyright (C) 2003  Mark Bucciarelli <mark@hubcapconsutling.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.
 *
 */

#ifndef KARM_STORAGE_H
#define KARM_STORAGE_H

#include <tqdict.h>
#include <tqptrstack.h>

#include "journal.h"
#include "reportcriteria.h"

#include "desktoplist.h"

#include <calendarresources.h>
#include <vector>
#include "resourcecalendar.h"
#include <kdemacros.h>

class TQDateTime;
class Preferences;
class Task;
class TaskView;
class HistoryEvent;

/**
 * Singleton to store/retrieve KArm data to/from persistent storage.
 *
 * The storage is an iCalendar file.  Also included are methods to
 * import KArm data from the two legacy file formats.
 *
 * All logic that deals with getting and saving data should go here.  The
 * storage logic has changed at least twice already in KArm's history, and
 * chances are good it will change again (for example, allowing KOrganizer and
 * KArm to access the same iCalendar file simultaneously).
 *
 * Prior to KDE 3.2, KArm just stored totals for each task--a session total
 * and a task total.  The session total was reset to zero each time KArm
 * started up or after the user reset the session times to zero.  With the
 * release of KDE 3.2, KArm now stores these task totals as well as logging
 * the history of each start/stop event; that is, every time you start a timer
 * and then stop a timer on a task, KArm records this as an iCalendar event.
 *
 * @short Logic that gets and stores KArm data to disk.
 * @author Mark Bucciarelli <mark@hubcapconsulting.com>
 */

class KarmStorage
{
  public:
    /*
     * Return reference to storage singleton.
     *
     * The constructors are private, so this must be used to create a
     * KarmStorage instance.
     */
    static KarmStorage *instance();

    /*
     * Load the list view with tasks read from iCalendar file.
     *
     * Parses iCalendar file, builds list view items in the proper
     * hierarchy, and loads them into the list view widget.
     *
     * If the file name passed in is the same as the last file name that was
     * loaded, this method does nothing.
     *
     * This method considers any of the following conditions errors:
     *
     *    @li the iCalendar file does not exist
     *    @li the iCalendar file is not readable
     *    @li the list group currently has list items
     *    @li an iCalendar todo has no related to attribute
     *    @li a todo is related to another todo which does not exist
     *
     * @param taskview     The list group used in the TaskView
     * @param preferences  The current KArm preferences.
     * @param fileName     Override preferences' filename
     *
     * @return empty string if success, error message if error.
     *
     */
    TQString load(TaskView* taskview, const Preferences* preferences, TQString fileName="" );

    /*
     * Return the name of the iCal file
     */
    TQString icalfile();

    /*
     * Build up the taskview.
     *
     * This is needed if the iCal file has been modified
     */
    TQString buildTaskView(KCal::ResourceCalendar *rc, TaskView *view);
    
    /* Close calendar and clear view.  Release lock if holding one. */
    void closeStorage(TaskView* view);

    /*
     * Save all tasks and their totals to an iCalendar file.
     *
     * All tasks must have an associated VTODO object already created in the
     * calendar file; that is, the task->uid() must refer to a valid VTODO in
     * the calender.
     * Delivers empty string if successful, else error msg.
     *
     * @param taskview    The list group used in the TaskView
     */
    TQString save(TaskView* taskview);

    /**
     * Read tasks and their total times from a text file (legacy storage).
     *
     * This reads from one of the two legacy file formats.  In this version,
     * the parent task times do not include the sum of all their children's
     * times.
     *
     * The format of the file is zero or more lines of:
     *    1         task id (a number)
     *    time      in minutes
     *    string    task name
     *    [string]  desktops, in which to count. e.g. "1,2,5" (optional)
     */
    TQString loadFromFlatFile(TaskView* taskview, const TQString& filename);

    /**
     *  Reads tasks and their total times from text file (legacy).
     *
     *  This is the older legacy format, where the task totals included the
     *  children totals.
     *
     *  @see loadFromFlatFile
     */
    TQString loadFromFlatFileCumulative(TaskView* taskview,
        const TQString& filename);

    /**
     Output a report based on contents of ReportCriteria.
     */
    TQString report( TaskView *taskview, const ReportCriteria &rc );

    /**
     * Log the change in a task's time.
     *
     * We create an iCalendar event to store each change.  The event start
     * date is set to the current datetime.  If time is added to the task, the
     * task end date is set to start time + delta.  If the time is negative,
     * the end date is set to the start time.
     *
     * In both cases (postive or negative delta), we create a custom iCalendar
     * property that stores the delta (in seconds).  This property is called
     * X-TDE-karm-duration.
     *
     * Note that the KArm UI allows the user to change both the session and
     * the total task time, and this routine does not account for all posibile
     * cases.  For example, it is possible for the user to do something crazy
     * like add 10 minutes to the session time and subtract 50 minutes from
     * the total time.  Although this change violates a basic law of physics,
     * it is allowed.
     *
     * For now, you should pass in the change to the total task time.
     * Eventually, the UI should be changed.
     *
     * @param task   The task the change is for.
     * @param delta  Change in task time, in seconds.  Can be negative.
     */
    void changeTime(const Task* task, const long deltaSeconds);

    /**
     * Book time to a task.
     *
     * Creates an iCalendar event and adds it to the calendar.  Does not write
     * calender to disk, just adds event to calendar in memory.  However, the
     * resource framework does try to get a lock on the file.  After a
     * succesful lock, the calendar marks this incidence as modified and then
     * releases the lock.
     *
     * @param task Task
     * @param startDateTime Date and time the booking starts.
     * @param durationInSeconds Duration of time to book, in seconds.
     *
     * @return true if event was added, false if not (if, for example, the
     * attempted file lock failed).
     */
    bool bookTime(const Task* task, const TQDateTime& startDateTime, 
                  long durationInSeconds);

    /**
     * Log a change to a task name.
     *
     * For iCalendar storage, there is no need to log an Event for this event,
     * since unique id's are used to link Events to Todos.  No matter how many
     * times you change a task's name, the uid stays the same.
     *
     * @param task     The task
     * @param oldname  The old name of the task.  The new name is in the task
     *   object already.
     */
    void setName(const Task* task, const TQString& oldname) { Q_UNUSED(task); Q_UNUSED(oldname); }


    /**
     * Log the event that a timer has started for a task.
     *
     * For the iCalendar storage, there is no need to log anything for this
     * event.  We log an event when the timer is stopped.
     *
     * @param task    The task the timer was started for.
     */
    void startTimer(const Task* task) { Q_UNUSED(task); }

    /**
     * Log the event that the timer has stopped for this task.
     *
     * The task stores the last time a timer was started, so we log a new iCal
     * Event with the start and end times for this task.
     * @see KarmStorage::changeTime
     *
     * @param task   The task the timer was stopped for.
     */
    void stopTimer(const Task* task, TQDateTime when=TQDateTime::currentDateTime());

    /**
     * Log a new comment for this task.
     *
     * iCal allows multiple comment tags.  So we just add a new comment to the
     * todo for this task and write the calendar.
     *
     * @param task     The task that gets the comment
     * @param comment  The comment
     */
    void addComment(const Task* task, const TQString& comment);


    /**
     * Remove this task from iCalendar file.
     *
     * Removes task as well as all event history for this task.
     *
     * @param task   The task to be removed.
     * @return true if change was saved, false otherwise
     */
    bool removeTask(Task* task);

    /**
     * Add this task from iCalendar file.
     *
     * Create a new KCal::Todo object and load with task information.  If
     * parent is not zero, then set the RELATED-TO attribute for this Todo.
     *
     * @param task   The task to be removed.
     * @param parent The parent of this task.  Must have a uid() that is in
     * the existing calendar.  If zero, this task is considered a root task.
     * @return The unique ID for the new VTODO.  Return an null TQString if
     * there was an error creating the new calendar object.
     */
    TQString addTask(const Task* task, const Task* parent);

    /**
     *  Check if the iCalendar file currently loaded has any Todos in it.
     *
     *  @return true if iCalendar file has any todos
     */
    bool isEmpty();

    /**
     * Check if iCalendar file name in the preferences has changed since the
     * last call to load.  If there is no calendar file currently loaded,
     * return false.
     *
     * @param preferences  Set of KArm preferences.
     *
     * @return true if a previous file has been loaded and the iCalendar file
     * specified in the preferences is different.
     */
    bool isNewStorage(const Preferences* preferences) const;

    /** Return a list of start/stop events for the given date range. */
    TQValueList<HistoryEvent> getHistory(const TQDate& from, const TQDate& to);

  private:
    static KarmStorage                *_instance;
    KCal::ResourceCalendar            *_calendar;
    TQString                           _icalfile;

    KarmStorage();
    void adjustFromLegacyFileFormat(Task* task);
    bool parseLine(TQString line, long *time, TQString *name, int *level,
        DesktopList* desktopList);
    TQString writeTaskAsTodo
      (Task* task, const int level, TQPtrStack< KCal::Todo >& parents);
    bool saveCalendar();

    KCal::Event* baseEvent(const Task*);
    bool remoteResource( const TQString& file ) const;

    /**
     *  Writes all tasks and their totals to a Comma-Separated Values file.
     *
     * The format of this file is zero or more lines of:
     *    taskName,subtaskName,..,sessionTime,time,totalSessionTime,totalTime
     * the number of subtasks is determined at runtime.
     */
    TQString exportcsvFile( TaskView *taskview, const ReportCriteria &rc );

    /**
     *  Write task history to file as comma-delimited data.
     */
    TQString exportcsvHistory (
            TaskView* taskview,
            const TQDate& from,
            const TQDate& to,
            const ReportCriteria &rc
            );

    long printTaskHistory (
            const Task *task,
            const TQMap<TQString,long>& taskdaytotals,
            TQMap<TQString,long>& daytotals,
            const TQDate& from,
            const TQDate& to,
            const int level, 
	    std::vector <TQString> &matrix,
            const ReportCriteria &rc
            );
};

/**
 * One start/stop event that has been logged.
 *
 * When a task is running and the user stops it, KArm logs this event and
 * saves it in the history.  This class represents such an event read from
 * storage, and abstracts it from the specific storage used.
 */
class HistoryEvent
{
  public:
    /** Needed to be used in a value list. */
    HistoryEvent() {}
    HistoryEvent(TQString uid, TQString name, long duration,
        TQDateTime start, TQDateTime stop, TQString todoUid);
    TQString uid() {return _uid; }
    TQString name() {return _name; }
    /** In seconds. */
    long duration() {return _duration; }
    TQDateTime start() {return _start; }
    TQDateTime stop() { return _stop; }
    TQString todoUid() {return _todoUid; }

  private:
    TQString _uid;
    TQString _todoUid;
    TQString _name;
    long _duration;
    TQDateTime _start;
    TQDateTime _stop;

};

#endif // KARM_STORAGE_H
