/*
 * Autopsy Forensic Browser
 *
 * Copyright 2018 Basis Technology Corp.
 * Contact: carrier <at> sleuthkit <dot> org
 *
 * Licensed 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.sleuthkit.autopsy.timeline;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import javafx.application.Platform;
import javax.swing.SwingUtilities;
import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.TskCoreException;

/**
 * Manages listeners and the controller.
 *
 */
public class TimeLineModule {

    private static final Logger logger = Logger.getLogger(TimeLineModule.class.getName());

    private static final Object controllerLock = new Object();
    private static volatile TimeLineController controller;

    /**
     * provides static utilities, can not be instantiated
     */
    private TimeLineModule() {
    }

    /**
     * Get instance of the controller for the current case
     *
     * @return the controller for the current case.
     *
     * @throws NoCurrentCaseException If there is no case open.
     * @throws TskCoreException       If there was a problem accessing the case
     *                                database.
     *
     */
    public static TimeLineController getController() throws NoCurrentCaseException, TskCoreException {
        synchronized (controllerLock) {
            if (controller == null) {
                controller = new TimeLineController(Case.getCurrentCaseThrows());
            }
            return controller;
        }
    }

    /**
     * This method is invoked by virtue of the OnStart annotation on the OnStart
     * class class
     */
    static void onStart() {
        Platform.setImplicitExit(false);
        logger.info("Setting up TimeLine listeners"); //NON-NLS

        IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
        Case.addPropertyChangeListener(new CaseEventListener());
    }

    /**
     * Listener for case events.
     */
    static private class CaseEventListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (Case.Events.valueOf(evt.getPropertyName()).equals(CURRENT_CASE)) {
                if (evt.getNewValue() == null) {
                    /*
                     * Current case is closing, shut down the timeline top
                     * component and set the pre case singleton controller
                     * reference to null.
                     */
                    synchronized (controllerLock) {
                        if (controller != null) {
                            controller.shutDownTimeLineListeners();
                            SwingUtilities.invokeLater(controller::shutDownTimeLineGui);
                        }
                        controller = null;
                    }
                }
            } else {
                try {
                    getController().handleCaseEvent(evt);
                } catch (NoCurrentCaseException ignored) {
                } catch (TskCoreException ex) {
                    logger.log(Level.SEVERE, "Error handling application event", ex);
                }
            }
        }
    }

    /**
     * Listener for IngestModuleEvents
     */
    static private class IngestModuleEventListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            try {
                getController().handleIngestModuleEvent(evt);
            } catch (NoCurrentCaseException ex) {
                // ignore
                return;
            } catch (TskCoreException ex) {
                MessageNotifyUtil.Message.error("Error creating timeline controller.");
                logger.log(Level.SEVERE, "Error creating timeline controller", ex);
            }
        }
    }
}

@@ -1761,24 +1761,95 @@
 current case
+.%0A     * The controller instance is initialized from a case open event.
 %0A     *%0A    
@@ -1907,72 +1907,8 @@
   *%0A
-     * @throws NoCurrentCaseException If there is no case open.%0A
     
@@ -2109,32 +2109,8 @@
 rows
- NoCurrentCaseException,
  Tsk
@@ -2223,34 +2223,46 @@
     
-controller = new 
+throw new TskCoreException(%22
 Time
-LineC
+line c
 ontr
@@ -2270,36 +2270,25 @@
 ller
-(Case.getCurrentCaseThrows()
+ not initialized%22
 );%0A 
@@ -3708,33 +3708,43 @@
                %7D
-%0A
+ else %7B%0A   
             %7D el
@@ -3735,33 +3735,135 @@
             
-%7D else %7B%0A
+     // Case is opening - create the controller now%0A                    synchronized (controllerLock) %7B%0A       
             
@@ -3858,32 +3858,33 @@
                 
+ 
 try %7B%0A          
@@ -3897,46 +3897,90 @@
     
-getController().handleCaseEvent(evt);%0A
+        controller = new TimeLineController(Case.getCurrentCaseThrows());%0A        
     
@@ -3992,32 +3992,51 @@
        %7D catch (
+TskCoreException %7C 
 NoCurrentCaseExc
@@ -4046,152 +4046,463 @@
 ion 
-ignored) %7B%0A                %7D catch (TskCoreException ex) %7B%0A                    logger.log(Level.SEVERE, %22Error handling application event%22, ex);
+ex) %7B%0A                            logger.log(Level.SEVERE, %22Error creating Timeline controller%22, ex);%0A                        %7D%0A                    %7D%0A                %7D%0A            %7D else %7B%0A                try %7B%0A                    getController().handleCaseEvent(evt);%0A                %7D catch (TskCoreException ex) %7B%0A                    // The call to getController() will only fail due to case closing, so do %0A                    // not record the error.
 %0A   
@@ -4871,28 +4871,22 @@
  catch (
-NoCurrentCas
+TskCor
 eExcepti
@@ -4917,254 +4917,119 @@
  // 
-ignore%0A                return;%0A            %7D catch (TskCoreException ex) %7B%0A                MessageNotifyUtil.Message.error(%22Error creating timeline controller.%22);%0A                logger.log(Level.SEVERE, %22Error creating timeline controller%22, ex);
+The call to getController() will only fail due to case closing, so do %0A                // not record the error.
 %0A   

Create timeline controller only at case open