/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.mylyn.util;

import java.awt.EventQueue;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.netbeans.modules.bugtracking.spi.IssueScheduleInfo;
import org.netbeans.modules.bugtracking.spi.IssueStatusProvider;
import org.netbeans.modules.bugtracking.commons.AttachmentsPanel;
import org.netbeans.modules.bugtracking.spi.IssueController;
import org.netbeans.modules.bugtracking.spi.IssueProvider;
import org.netbeans.modules.bugtracking.spi.IssueScheduleProvider;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;

/**
 *
 * @author Ondrej Vrabec
 */
public abstract class AbstractNbTaskWrapper {
    
    static final String ATTR_NEW_UNREAD = "NetBeans.task.markedNewUnread"; //NOI18N
    private static final Object MODEL_LOCK = new Object();
    private static final Logger LOG = Logger.getLogger(AbstractNbTaskWrapper.class.getName());
    private static final RequestProcessor RP = new RequestProcessor("NBTasks"); //NOI18N
    public static final String NEW_ATTACHMENT_ATTRIBUTE_ID = "nb.attachments.new"; //NOI18N
    private static final String NB_NEW_ATTACHMENT_PATCH_ATTR_ID = "nb.newattachment.patch"; //NOI18N
    private static final String NB_NEW_ATTACHMENT_FILE_ATTR_ID = "nb.newattachment.file"; //NOI18N
    private static final String NB_NEW_ATTACHMENT_CONTENT_TYPE_ATTR_ID = "nb.newattachment.contentType"; //NOI18N
    private static final String NB_NEW_ATTACHMENT_DESC_ATTR_ID = "nb.newattachment.desc"; //NOI18N
    private static final String NB_NEW_ATTACHMENT_ATTR_ID = "nb.newattachment."; //NOI18N

    private NbTask task;
    private NbTaskDataModel model;
    private NbTaskDataModel.NbTaskDataModelListener list;
    private boolean readPending;
    private final TaskDataListenerImpl taskDataListener;
    private final TaskListenerImpl taskListener;
    private Reference<TaskData> repositoryDataRef;
    private final RequestProcessor.Task repositoryTaskDataLoaderTask;
    private final PropertyChangeSupport support;
    
    /** PRIVATE TASK ATTRIBUTES **/
    private String privateNotes;
    private Date dueDate;
    private boolean dueDateModified;
    private NbDateRange scheduleDate;
    private boolean scheduleDateModified;
    private Integer estimate;
    /** PRIVATE TASK ATTRIBUTES **/

    public AbstractNbTaskWrapper (NbTask task) {
        this.task = task;
        this.repositoryDataRef = new SoftReference<TaskData>(null);
        support = new PropertyChangeSupport(this);
        repositoryTaskDataLoaderTask = RP.create(new Runnable() {
            @Override
            public void run () {
                loadRepositoryTaskData();
            }
        });
        MylynSupport mylynSupp = MylynSupport.getInstance();
        taskDataListener = new TaskDataListenerImpl();
        mylynSupp.addTaskDataListener(WeakListeners.create(TaskDataListener.class, taskDataListener, mylynSupp));
        taskListener = new TaskListenerImpl();
        task.addNbTaskListener(WeakListeners.create(NbTaskListener.class, taskListener, mylynSupp));
    }

    /**
     * Returns the id of the given task or null if task is new
     * @param task
     * @return id or null
     */
    public static String getID (NbTask task) {
        if (task.isUnsubmittedRepositoryTask()) {
            return "-" + task.getTaskId();
        }
        return task.getTaskId();
    }

    protected static boolean attachmentAttributesDiffer (TaskAttribute ta1, TaskAttribute ta2) {
        if (ta2 == null) {
            return true;
        }
        String value1 = ta1.getValue();
        String value2 = ta2.getValue();
        boolean changes = !value1.equals(value2);
        if (!changes) {
            // is a child attribue changed??
            for (TaskAttribute childTA : ta1.getAttributes().values()) {
                if (attachmentAttributesDiffer(childTA, ta2.getAttribute(childTA.getId()))) {
                    changes = true;
                    break;
                }
            }
        }
        return changes;
    }

    public final void addPropertyChangeListener (PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    public final void removePropertyChangeListener (PropertyChangeListener listener) {
        support.removePropertyChangeListener(listener);
    }

    protected final TaskData getRepositoryTaskData () {
        TaskData taskData = repositoryDataRef.get();
        if (taskData == null) {
            if (EventQueue.isDispatchThread()) {
                repositoryTaskDataLoaderTask.schedule(100);
            } else {
                return loadRepositoryTaskData();
            }
        }
        return taskData;
    }

    private TaskData loadRepositoryTaskData () {
        // this method is time consuming
        assert !EventQueue.isDispatchThread();
        TaskData td = repositoryDataRef.get();
        if (td != null) {
            return td;
        }
        try {
            NbTaskDataState taskDataState = task.getTaskDataState();
            if (taskDataState != null) {
                td = taskDataState.getRepositoryData();
                repositoryDataRef = new SoftReference<TaskData>(td);
                repositoryTaskDataLoaded(td);
            }
        } catch (CoreException ex) {
            LOG.log(Level.WARNING, null, ex);
        }
        return td;
    }

    protected final void deleteTask () {
        assert task.isLocal() : "Only local tasks can be deleted: " + task.getDelegate().getConnectorKind();
        synchronized (MODEL_LOCK) {
            if (list != null) {
                model.removeNbTaskDataModelListener(list);
                list = null;
            }
            model = null;
        }
        MylynSupport mylynSupp = MylynSupport.getInstance();
        mylynSupp.removeTaskDataListener(taskDataListener);
        task.removeNbTaskListener(taskListener);
        if (task.isLocal()) {
            task.delete();
            taskDeleted(task);
            fireDeleted();
        }
    }

    protected abstract void taskDeleted (NbTask task);

    public final boolean isMarkedNewUnread () {
        return task.isLocal() && Boolean.TRUE.toString().equals(task.getAttribute(ATTR_NEW_UNREAD));
    }

    public final boolean isNew () {
        return task.isUnsubmittedRepositoryTask();
    }

    protected final void markNewRead () {
        task.setAttribute(ATTR_NEW_UNREAD, null);
    }

    public final void setUpToDate (boolean seen, boolean markReadPending) {
        synchronized (MODEL_LOCK) {
            if (markReadPending) {
                // this is a workaround to keep incoming changes visible in editor
                NbTask.SynchronizationState syncState = task.getSynchronizationState();
                readPending |= syncState == NbTask.SynchronizationState.INCOMING
                        || syncState == NbTask.SynchronizationState.CONFLICT;
            } else {
                readPending = false;
            }
            task.markSeen(seen);
        }
    }

    protected final boolean editorOpened () {
        list = new NbTaskDataModel.NbTaskDataModelListener() {
            @Override
            public void attributeChanged (NbTaskDataModel.NbTaskDataModelEvent event) {
                NbTaskDataModel m = model;
                if (event.getModel() == m) {
                    AbstractNbTaskWrapper.this.attributeChanged(event, m);
                }
            }
        };
        if (task.getSynchronizationState() == NbTask.SynchronizationState.INCOMING_NEW) {
            // mark as seen so no fields are highlighted
            setUpToDate(true, false);
        }
        synchronized (MODEL_LOCK) {
            synchronized (getNbTask()) {
                // clear upon close
                if (readPending) {
                    // make sure remote changes are not lost and still highlighted in the editor
                    setUpToDate(false, false);
                    if (task.getDelegate() instanceof AbstractTask) {
                        ((AbstractTask) task.getDelegate()).setMarkReadPending(false);
                    }
                }
                model = task.getTaskDataModel();
                if (model == null) {
                    if (!synchronizeTask()) {
                        return false;
                    }
                    model = task.getTaskDataModel();
                }
                if(model == null) {
                    // issue #247877 
                    // something went wrong, not much we can do about it at this place. 
                    // lets avoid at least the NPE
                    LOG.log(Level.WARNING, "no model even after synchronizing {0}", task.getTaskId());
                    return false;
                }
                model.addNbTaskDataModelListener(list);
            }
            try {
                MylynSupport.getInstance().editorOpened(task.getDelegate());
            } catch (CoreException ex) {
                LOG.log(Level.WARNING, null, ex);
            }
        }
        return true;
    }

    protected final void editorClosed () {
        NbTaskDataModel m = model;
        boolean markedAsNewUnread = isMarkedNewUnread();
        if (m != null) {
            if (list != null) {
                m.removeNbTaskDataModelListener(list);
                list = null;
            }
            readPending = false;
            if (markedAsNewUnread) {
                // was not modified by user and not yet saved
                deleteTask();
            } else {
                if (m.isDirty()) {
                    try {
                        save();
                    } catch (CoreException ex) {
                        LOG.log(Level.WARNING, null, ex);
                    }
                }
                try {
                    MylynSupport.getInstance().editorClosing(task.getDelegate(), m.getRepositoryTaskData());
                } catch (CoreException ex) {
                    LOG.log(Level.WARNING, null, ex);
                }
                synchronized (MODEL_LOCK) {
                    if (model == m) {
                        model = null;
                    }
                }
            }
        }
    }

    protected final void runWithModelLoaded (Runnable runnable) {
        synchronized (MODEL_LOCK) {
            boolean closeModel = false;
            try {
                if (model == null) {
                    closeModel = true;
                    model = task.getTaskDataModel();
                }
                runnable.run();
            } finally {
                if (closeModel) {
                    if (model != null && model.isDirty()) {
                        try {
                            // let's not loose edits
                            model.save();
                        } catch (CoreException ex) {
                            LOG.log(Level.INFO, null, ex);
                        }
                    }
                    model = null;
                }
            }
        }
    }

    protected abstract void attributeChanged (NbTaskDataModel.NbTaskDataModelEvent event, NbTaskDataModel model);

    private void save () throws CoreException {
        NbTaskDataModel m = this.model;
        if(m == null) {
            // haven't been opened yet so there is no model?
            // might happen when e.g. closing a local task on commit (see issue #252222)
            m = task.getTaskDataModel();
        }
        markNewRead();
        boolean modified = persistPrivateTaskAttributes();
        if (m.isDirty()) {
            if (isNew()) {
                String summary = task.getSummary();
                String newSummary = getSummary(m.getLocalTaskData());
                if (newSummary != null && !(newSummary.isEmpty() || newSummary.equals(summary))) {
                    task.setSummary(newSummary);
                    taskModified(false);
                }
            }
            m.save();
            modelSaved(m);
        }
        if (modified) {
            taskModified(false);
        }
    }
    
    protected final void taskSubmitted (NbTask task) {
        if (task != null && task != this.task) {
            this.task.removeNbTaskListener(taskListener);
            this.task = task;
            task.addNbTaskListener(taskListener);
            synchronized (MODEL_LOCK) {
                if (list != null) {
                    model.removeNbTaskDataModelListener(list);
                }
                model = task.getTaskDataModel();
                repositoryDataRef.clear();
                if (list != null) {
                    model.addNbTaskDataModelListener(list);
                }
            }
        }
    }

    protected final boolean saveChanges () {
        try {
            save();
            return true;
        } catch (CoreException ex) {
            LOG.log(Level.WARNING, null, ex);
        }
        return false;
    }

    public final boolean cancelChanges () {
        try {
            if (saveChanges()) {
                task.discardLocalEdits();
                model.refresh();
                return true;
            }
        } catch (CoreException ex) {
            LOG.log(Level.WARNING, null, ex);
        }
        return false;
    }

    public final void clearUnsavedChanges () {
        clearPrivateTaskAttributeChanges();
        model.clearUnsavedChanges();
    }

    public final boolean hasLocalEdits () {
        NbTaskDataModel m = model;
        return !(m == null || m.getChangedAttributes().isEmpty());
    }
    
    public final boolean hasUnsavedChanges () {
        NbTaskDataModel m = model;
        return m != null && m.isDirty()
                || hasUnsavedPrivateTaskAttributes();
    }

    protected final boolean updateModel () {
        try {
            model.refresh();
            MylynSupport.getInstance().editorOpened(task.getDelegate());
            return true;
        } catch (CoreException ex) {
            LOG.log(Level.INFO, null, ex);
        }
        return false;
    }

    protected abstract void modelSaved (NbTaskDataModel model);

    protected abstract String getSummary (TaskData taskData);

    protected abstract void taskDataUpdated ();

    protected final boolean isSeen () {
        NbTask.SynchronizationState syncState = task.getSynchronizationState();
        return syncState == NbTask.SynchronizationState.OUTGOING
                || syncState == NbTask.SynchronizationState.OUTGOING_NEW
                || syncState == NbTask.SynchronizationState.SYNCHRONIZED;
    }

    protected abstract void taskModified (boolean syncStateChanged);

    protected final NbTaskDataModel getModel () {
        return model;
    }

    protected abstract void repositoryTaskDataLoaded (TaskData repositoryTaskData);

    protected final NbTask getNbTask () {
        return task;
    }

    public final long getCreated () {
        Date createdDate = getCreatedDate();
        if (createdDate != null) {
            return createdDate.getTime();
        } else {
            return -1;
        }
    }

    public final Date getCreatedDate () {
        return task.getCreationDate();
    }

    public final long getLastModify () {
        Date lastModifyDate = getLastModifyDate();
        if (lastModifyDate != null) {
            return lastModifyDate.getTime();
        } else {
            return -1;
        }
    }

    public final Date getLastModifyDate () {
        return task.getModificationDate();
    }

    public final String getSummary () {
        return task.getSummary();
    }

    public final String getID () {
        return getID(task);
    }

    public final boolean isFinished () {
        return task.isCompleted();
    }

    protected final NbTask.SynchronizationState getSynchronizationState () {
        return task.getSynchronizationState();
    }

    public final IssueStatusProvider.Status getStatus () {
        switch (getSynchronizationState()) {
            case CONFLICT:
                return IssueStatusProvider.Status.CONFLICT;
            case INCOMING:
                return IssueStatusProvider.Status.INCOMING_MODIFIED;
            case INCOMING_NEW:
                return IssueStatusProvider.Status.INCOMING_NEW;
            case OUTGOING:
                return IssueStatusProvider.Status.OUTGOING_MODIFIED;
            case OUTGOING_NEW:
                return IssueStatusProvider.Status.OUTGOING_NEW;
            case SYNCHRONIZED:
                return IssueStatusProvider.Status.SEEN;
        }
        return null;
    }

    /**
     * Called when the task does not yet contain all needed data. Needs to re-sync to get full taskdata.
     * @return false when the sync fails for some reason
     */
    protected abstract boolean synchronizeTask ();

    protected final boolean setNewAttachments (List<AttachmentsPanel.AttachmentInfo> newAttachments) {
        NbTaskDataModel m = getModel();
        TaskData td = m.getLocalTaskData();
        TaskAttribute ta = td.getRoot().getAttribute(AbstractNbTaskWrapper.NEW_ATTACHMENT_ATTRIBUTE_ID);
        Map<String, TaskAttribute> previousAttachments = Collections.<String, TaskAttribute>emptyMap();
        if (ta != null) {
            previousAttachments = ta.getAttributes();
            ta.clearAttributes();
        }
        boolean retval = false;
        if (newAttachments.isEmpty() && !previousAttachments.isEmpty()) {
            m.attributeChanged(ta);
            retval = true;
        } else if (!newAttachments.isEmpty()) {
            if (ta == null) {
                ta = td.getRoot().createAttribute(AbstractNbTaskWrapper.NEW_ATTACHMENT_ATTRIBUTE_ID);
            }
            boolean present = true;
            for (int i = 0; i < newAttachments.size(); ++i) {
                AttachmentsPanel.AttachmentInfo att = newAttachments.get(i);
                TaskAttribute attAttr = ta.createAttribute(NB_NEW_ATTACHMENT_ATTR_ID + i);
                if (att.getDescription() != null) {
                    attAttr.createAttribute(NB_NEW_ATTACHMENT_DESC_ATTR_ID).setValue(att.getDescription());
                }
                if (att.getContentType() != null) {
                    attAttr.createAttribute(NB_NEW_ATTACHMENT_CONTENT_TYPE_ATTR_ID).setValue(att.getContentType());
                }
                if (att.getFile() != null) {
                    attAttr.createAttribute(NB_NEW_ATTACHMENT_FILE_ATTR_ID).setValue(att.getFile().getAbsolutePath());
                }
                attAttr.createAttribute(NB_NEW_ATTACHMENT_PATCH_ATTR_ID).setValue(att.isPatch() ? "1" : "0");
                if (present) {
                    present = false;
                    for (TaskAttribute previousAttachment : previousAttachments.values()) {
                        if (!attachmentAttributesDiffer(previousAttachment, ta)) {
                            present = true;
                            break;
                        }
                    }
                }
            }
            if (!present || previousAttachments.size() != newAttachments.size()) {
                m.attributeChanged(ta);
                retval = true;
            }
        }
        return retval;
    }

    protected final List<AttachmentsPanel.AttachmentInfo> getNewAttachments () {
        NbTaskDataModel m = getModel();
        TaskData td = m == null ? null : m.getLocalTaskData();
        List<AttachmentsPanel.AttachmentInfo> attachments = Collections.<AttachmentsPanel.AttachmentInfo>emptyList();
        if (td != null) {
            TaskAttribute ta = td.getRoot().getAttribute(AbstractNbTaskWrapper.NEW_ATTACHMENT_ATTRIBUTE_ID);
            if (ta != null) {
                Map<String, TaskAttribute> attributes = ta.getAttributes();
                attachments = new ArrayList<AttachmentsPanel.AttachmentInfo>(attributes.size());
                for (TaskAttribute attAttr : attributes.values()) {
                    AttachmentsPanel.AttachmentInfo attInfo = new AttachmentsPanel.AttachmentInfo();
                    attInfo.setDescription("");
                    attInfo.setFile(null);
                    attInfo.setIsPatch(false);
                    for (Map.Entry<String, TaskAttribute> attSubAttr : attAttr.getAttributes().entrySet()) {
                        if (NB_NEW_ATTACHMENT_DESC_ATTR_ID.equals(attSubAttr.getKey())) {
                            attInfo.setDescription(attSubAttr.getValue().getValue());
                        } else if (NB_NEW_ATTACHMENT_CONTENT_TYPE_ATTR_ID.equals(attSubAttr.getKey())) {
                            attInfo.setContentType(attSubAttr.getValue().getValue());
                        } else if (NB_NEW_ATTACHMENT_FILE_ATTR_ID.equals(attSubAttr.getKey())) {
                            attInfo.setFile(new File(attSubAttr.getValue().getValue()));
                        } else if (NB_NEW_ATTACHMENT_PATCH_ATTR_ID.equals(attSubAttr.getKey())) {
                            attInfo.setIsPatch("1".equals(attSubAttr.getValue().getValue()));
                        }
                    }
                    attachments.add(attInfo);
                }
            }
        }
        return attachments;
    }

    protected final void setPrivateNotes (String text) {
        privateNotes = text;
    }

    public final String getPrivateNotes () {
        if (privateNotes == null) {
            return getNbTask().getPrivateNotes();
        } else {
            return privateNotes;
        }
    }

    public final Date getDueDate () {
        return dueDateModified ? dueDate : getNbTask().getDueDate();
    }

    protected final void setDueDate (Date date, boolean persistentChange) {
        dueDate = date;
        dueDateModified = true;
        if (persistentChange) {
            persistDueDate();
        }
    }

    public final NbDateRange getScheduleDate () {
        return scheduleDateModified ? scheduleDate : getNbTask().getScheduleDate();
    }

    protected final void setScheduleDate (IssueScheduleInfo info, boolean persistentChange) {
        scheduleDate = info == null ? null : new NbDateRange(info);
        scheduleDateModified = true;
        if (persistentChange) {
            persistScheduleDate();
        }
    }

    public final int getEstimate () {
        return estimate == null ? getNbTask().getEstimate() : estimate;
    }

    protected final void setEstimate (int estimate, boolean persistentChange) {
        this.estimate = estimate;
        if (persistentChange) {
            persistEstimate();
        }
    }

    /**
     * Returns only persistent value of due date, never the unsaved modification from the task editor
     * @return current task's persistent due date value
     */
    public final Date getPersistentDueDate () {
        return getNbTask().getDueDate();
    }

    /**
     * Returns only persistent value of estimate, never the unsaved modification from the task editor
     * @return current task's persistent estimate value
     */
    public final int getPersistentEstimate () {
        return getNbTask().getEstimate();
    }

    /**
     * Returns only persistent value of schedule date, never the unsaved modification from the task editor
     * @return current task's persistent scheduled date value
     */
    public final IssueScheduleInfo getPersistentScheduleInfo () {
        NbDateRange scheduled = getNbTask().getScheduleDate();
        return scheduled == null ? null : scheduled.toSchedulingInfo();
    }

    protected final boolean hasUnsavedPrivateTaskAttributes () {
        return privateNotes != null || dueDateModified || scheduleDateModified || estimate != null;
    }

    private boolean persistPrivateTaskAttributes () {
        boolean modified = false;
        if (privateNotes != null) {
            getNbTask().setPrivateNotes(privateNotes.isEmpty() ? null : privateNotes);
            privateNotes = null;
            modified = true;
        }
        boolean fireScheduleEvent = false;
        if (persistEstimate()) {
            modified = true;
            fireScheduleEvent = true;
        }
        if (persistDueDate()) {
            modified = true;
            fireScheduleEvent = true;
        }
        if (persistScheduleDate()) {
            modified = true;
            fireScheduleEvent = true;
        }
        if (fireScheduleEvent) {
            fireScheduleChanged();
        }
        return modified;
    }

    private boolean persistDueDate () {
        boolean modified = false;
        if (dueDateModified) {
            getNbTask().setDueDate(dueDate);
            dueDate = null;
            dueDateModified = false;
            modified = true;
        }
        return modified;
    }

    private boolean persistEstimate () {
        boolean modified = false;
        if (estimate != null) {
            getNbTask().setEstimate(estimate);
            estimate = null;
            modified = true;
        }
        return modified;
    }

    private boolean persistScheduleDate () {
        boolean modified = false;
        if (scheduleDateModified) {
            getNbTask().setScheduleDate(scheduleDate);
            scheduleDate = null;
            scheduleDateModified = false;
            modified = true;
        }
        return modified;
    }

    private void clearPrivateTaskAttributeChanges () {
        privateNotes = null;
        dueDate = null;
        dueDateModified = false;
        scheduleDate = null;
        scheduleDateModified = false;
        estimate = null;
    }

    protected final String getDueDisplayString () {
        Calendar cal = Calendar.getInstance();
        Date date = getPersistentDueDate();
        if (date == null) {
            return "";
        }
        cal.setTime(date);
        return formatDate(cal);
    }

    protected final String getEstimateDisplayString () {
        int est = getPersistentEstimate();
        if (est == 0) {
            return "";
        }
        return "" + est;
    }

    protected final String getScheduleDisplayString () {
        NbDateRange schedule = getNbTask().getScheduleDate();
        if (schedule == null) {
            return "";
        }
        int interval = schedule.getEndDate().get(Calendar.DAY_OF_YEAR) - schedule.getStartDate().get(Calendar.DAY_OF_YEAR);
        if (interval == 1) {
            return formatDate(schedule.getStartDate());
        }
        return formateDate(schedule.getStartDate(), schedule.getEndDate());
    }

    protected final void fireChanged () {
        support.firePropertyChange(IssueController.PROP_CHANGED, null, null);
    }

    protected final void fireDataChanged () {
        support.firePropertyChange(IssueProvider.EVENT_ISSUE_DATA_CHANGED, null, null);
    }

    protected final void fireScheduleChanged () {
        support.firePropertyChange(IssueScheduleProvider.EVENT_ISSUE_SCHEDULE_CHANGED, null, null);
    }

    protected final void fireStatusChanged () {
        support.firePropertyChange(IssueStatusProvider.EVENT_STATUS_CHANGED, null, null);
    }

    private void fireDeleted () {
        support.firePropertyChange(IssueProvider.EVENT_ISSUE_DELETED, null, null);
    }

    private String formatDate (Calendar date) {
        return DateFormat.getDateInstance(DateFormat.DEFAULT).format(date.getTime());
    }

    private String formateDate (Calendar start, Calendar end) {
        DateFormat format = DateFormat.getDateInstance(DateFormat.DEFAULT);
        return format.format(start.getTime()) + " - " + format.format(end.getTime()); //NOI18N
    }

    private class TaskDataListenerImpl implements TaskDataListener {

        @Override
        public void taskDataUpdated (TaskDataListener.TaskDataEvent event) {
            if (event.getTask() == task) {
                if (event.getTaskData() != null && !event.getTaskData().isPartial()) {
                    repositoryDataRef = new SoftReference<TaskData>(event.getTaskData());
                }
                if (event.getTaskDataUpdated()) {
                    NbTaskDataModel m = model;
                    if (m != null) {
                        try {
                            m.refresh();
                        } catch (CoreException ex) {
                            LOG.log(Level.INFO, null, ex);
                        }
                    }
                    AbstractNbTaskWrapper.this.taskDataUpdated();
                }
            }
        }
    }

    private class TaskListenerImpl implements NbTaskListener {

        @Override
        public void taskModified (NbTaskListener.TaskEvent event) {
            if (event.getTask() == task && event.getKind() == NbTaskListener.TaskEvent.Kind.MODIFIED) {
                boolean syncStateChanged = event.taskStateChanged();
                AbstractNbTaskWrapper.this.taskModified(syncStateChanged);
            }
        }

    }
}
