package edu.sc.seis.gee.task;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import org.apache.log4j.Category;
import edu.iris.Fissures.IfEvent.EventAccessOperations;
import edu.iris.Fissures.model.MicroSecondDate;
import edu.iris.Fissures.seismogramDC.LocalSeismogramImpl;
import edu.sc.seis.fissuresUtil.cache.AbstractJob;
import edu.sc.seis.fissuresUtil.cache.EventUtil;
import edu.sc.seis.fissuresUtil.cache.WorkerThreadPool;
import edu.sc.seis.fissuresUtil.display.BasicSeismogramDisplay;
import edu.sc.seis.fissuresUtil.display.DisplayUtils;
import edu.sc.seis.fissuresUtil.display.MultiSeismogramWindowDisplay;
import edu.sc.seis.fissuresUtil.display.RecordSectionDisplay;
import edu.sc.seis.fissuresUtil.display.SeismogramDisplay;
import edu.sc.seis.fissuresUtil.display.VerticalSeismogramDisplay;
import edu.sc.seis.fissuresUtil.display.drawable.DrawableIterator;
import edu.sc.seis.fissuresUtil.display.drawable.DrawableSeismogram;
import edu.sc.seis.fissuresUtil.display.drawable.Event;
import edu.sc.seis.fissuresUtil.exceptionHandler.GlobalExceptionHandler;
import edu.sc.seis.fissuresUtil.freq.NamedFilter;
import edu.sc.seis.fissuresUtil.xml.DataSet;
import edu.sc.seis.fissuresUtil.xml.DataSetSeismogram;
import edu.sc.seis.fissuresUtil.xml.MemoryDataSetSeismogram;
import edu.sc.seis.gee.CommonAccess;
import edu.sc.seis.gee.NoNetworkException;
import edu.sc.seis.gee.configurator.ConfigurationException;
import edu.sc.seis.gee.task.dataSetBrowser.DataSetBrowser;
import edu.sc.seis.gee.task.dataSetBrowser.SeismogramSelectedListener;
import edu.sc.seis.gee.task.dataSetBrowser.SeismogramSelectionEvent;

/**
 * ColumnSeismogramTask.java Created: Thu Jan 31 11:26:52 2002
 * 
 * @author <a href="mailto:">Philip Crotwell </a>
 * @version
 */
public class ColumnSeismogramTask extends JPanel implements Task, FilterTarget {

    public ColumnSeismogramTask() {
        setLayout(new BorderLayout());
        SeismogramDisplay.setMouseMotionForwarder(GlobalToolBar.getMouseMotionForwarder());
        SeismogramDisplay.setMouseForwarder(GlobalToolBar.getMouseForwarder());
        timeAmpLabelTask = new TimeAmpLabelTask();
        columnSeismogramTaskCount++;
    }

    public void configure(Map params) throws ConfigurationException {
        CommonAccess ca = CommonAccess.getCommonAccess();
        this.params = params;
        timeAmpLabelTask.configure(params);
        timeAmpLabelTask.invoke();
        if(params.containsKey("datasetbrowser")) {
            String browserId = (String)params.get("datasetbrowser");
            TaskAction browserAction = ca.getTaskAction(browserId);
            DataSetBrowser browser = (DataSetBrowser)browserAction.getTask();
            browser.addSeismogramSelectedListener(new SeismogramSelectedListener() {

                public void seismogramSelected(SeismogramSelectionEvent e) {
                    if(e.isSelected()) {
                        final DataSetSeismogram seis = e.getSeismogram();
                        WorkerThreadPool.getDefaultPool()
                                .invokeLater(new AbstractJob("Loading "
                                        + e.getSeismogramName()
                                        + " into display") {

                                    public void runJob() {
                                        addSeismogram(seis);
                                        TaskAction ta = AllTasks.getTaskAction(ColumnSeismogramTask.this);
                                        try {
                                            ta.checkDisplayLocation();
                                        } catch(ConfigurationException ex) {}
                                    }
                                });
                    } // end of if (e.isSelected())
                }
            });
        } // end of if (params.containsKey("datasetbrowser"))
        if(params.containsKey("rangeSelectionTask"))
            rangeSelTA = ca.getTaskAction((String)params.get("rangeSelectionTask"));
        if(params.containsKey("customPickTask")) {
            customPickTA = ca.getTaskAction((String)params.get("customPickTask"));
        }
        if(params.containsKey("safeDisplayMaker")) {
            String dmId = (String)params.get("safeDisplayMaker");
            safeDisplayMakerTA = ca.getTaskAction(dmId);
        } else {
            throw new ConfigurationException("Column Seismogram task requires a safeDisplayMaker");
        }
        if(params.containsKey("defaultDisplayMaker")) {
            String dmId = (String)params.get("defaultDisplayMaker");
            defaultDisplayMakerTA = ca.getTaskAction(dmId);
            setDisplayMaker((DisplayMakerTask)defaultDisplayMakerTA.getTask());
        } else {
            setDisplayMaker((DisplayMakerTask)safeDisplayMakerTA.getTask());
        }
        if(params.containsKey("safeTimeSetter")) {
            String tsId = (String)params.get("safeTimeSetter");
            safeTimeSetter = (TimeSetterTask)ca.getTaskAction(tsId).getTask();
            setTimeSetter(safeTimeSetter);
        } else {
            throw new ConfigurationException("Column Seismogram task requires a safeTimeConfig");
        }
        if(params.containsKey("TauP")) {
            String taupID = (String)params.get("TauP");
            taupTaskAction = ca.getTaskAction(taupID);
        }
        if(params.containsKey("Placeholder")) {
            String placeholderID = (String)params.get("Placeholder");
            TaskAction placeholderTA = ca.getTaskAction(placeholderID);
            placeholder = (PlaceholderImage)placeholderTA.getTask();
        }
    }

    public void invoke() throws ConfigurationException, NoNetworkException {
        if(placeholder != null) {
            add(placeholder.generateImagePanel());
            placeholder = null;
        }
    }

    public void destroy() {
        if(defaultDisplayMakerTA != null) {
            defaultDisplayMakerTA.reset();
        }
        safeDisplayMakerTA.reset();
        timeAmpLabelTask.destroy();
        removeAllDisplays();
        columnSeismogramTaskCount--;
        if(columnSeismogramTaskCount == 0) {
            BasicSeismogramDisplay.getActiveFilters().clear();
        }
    }

    private int columnSeismogramTaskCount = 0;

    public void addSeismogram(LocalSeismogramImpl seis, DataSet ds) {
        addSeismogram(new MemoryDataSetSeismogram(seis, ds));
    }

    public void addSeismogram(DataSetSeismogram dss) {
        if(orderedSeis.contains(dss)) {
            orderedSeis.remove(dss);
        }
        orderedSeis.addLast(dss);
        makeSeismogramDisplayVisible();
        // *
        if((careAboutLackOfEventForDisplay || careAboutLackOfEventForTime)
                && (displayMaker.needsEventInformation() || (timeSetter != null && getTimeSetter().needsEventInformation()))) {
            if(!hasDistance(dss)) {
                if(getDisplay().getSeismograms().length == 0) {
                    setTimeSetter(safeTimeSetter);
                    safeDisplayMakerTA.invoke(false);
                } else {
                    int decision = JOptionPane.showConfirmDialog(this,
                                                                 "You are attempting to add a seismogram with no event information to a display that requires it.\nWould you like to switch to a display type that supports this?",
                                                                 "Adding seismogram with no event",
                                                                 JOptionPane.YES_NO_OPTION,
                                                                 JOptionPane.QUESTION_MESSAGE);
                    if(decision == JOptionPane.YES_OPTION) {
                        if(displayMaker.needsEventInformation()) {
                            if(timeSetter != null
                                    && getTimeSetter().needsEventInformation()) {
                                // both time and display need event info
                                setTimeSetter(safeTimeSetter);
                                safeDisplayMakerTA.invoke(false);
                            } else {
                                // just display needs event info
                                safeDisplayMakerTA.invoke(false);
                            }
                        } else {
                            // just time needs event info
                            setTimeSetter(safeTimeSetter);
                        }
                    } else {
                        careAboutLackOfEventForDisplay = false;
                        careAboutLackOfEventForTime = false;
                    }
                }
            }
        }// */
        DataSetSeismogram[] seismos = {dss};
        getDisplay().add(seismos);
        if(ampSetter != null) {
            getAmpSetter().setAmp(getDisplay());
        }
        if(taupulate) {
            displayTravelTimes(getDisplay());
        }
    }

    public void removeSeismogram(DataSetSeismogram dss) {
        DataSetSeismogram[] seis = {dss};
        getDisplay().remove(seis);
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
    }

    /** checks if travel times should be displayed, and if so, displays them */
    public void checkTravelTimes() {
        if(taupulate)
            displayTravelTimes(getDisplay());
    }

    public void displayTravelTimes() {
        displayTravelTimes(getDisplay());
    }

    private void displayTravelTimes(SeismogramDisplay display) {
        if(taupTaskAction == null)
            return;
        boolean shownDialog = false;
        try {
            Iterator it = display.iterator(DrawableSeismogram.class);
            while(it.hasNext()) {
                DrawableSeismogram curSeis = (DrawableSeismogram)it.next();
                DataSetSeismogram curDSS = curSeis.getSeismogram();
                if(hasDistance(curDSS)) {
                    if(display instanceof MultiSeismogramWindowDisplay) {
                        curSeis.add(new Event(((TauPTask)taupTaskAction.getTask()).getArrivals(curDSS),
                                              getEventTime(curDSS),
                                              curSeis),
                                    Color.RED);
                    } else {
                        curSeis.add(new Event(((TauPTask)taupTaskAction.getTask()).getArrivals(curDSS),
                                              getEventTime(curDSS),
                                              curSeis));
                    }
                } else if(!shownDialog) {
                    JOptionPane.showMessageDialog(this,
                                                  "You are attempting to show travel times for a seismogram without enough information to calculate them.\nNo flags will be shown for seismograms without this information.",
                                                  "Some seismograms have no distance",
                                                  JOptionPane.INFORMATION_MESSAGE);
                    shownDialog = true;
                }
            }
            taupulate = true;
        } catch(ConfigurationException e) {
            GlobalExceptionHandler.handle("Travel time calculator is not configured correctly.",
                                          e);
        }
    }

    private MicroSecondDate getEventTime(DataSetSeismogram seis) {
        DataSet ds = seis.getDataSet();
        EventAccessOperations eao = ds.getEvent();
        if(eao != null) {
            return new MicroSecondDate(EventUtil.extractOrigin(eao).getOriginTime());
        }
        return null;
    }

    public void removeAllDisplays() {
        getDisplay().clear();
    }

    public void removeTravelTimes() {
        DrawableIterator it = getDisplay().iterator(DrawableSeismogram.class);
        while(it.hasNext()) {
            DrawableSeismogram curSeis = (DrawableSeismogram)it.next();
            curSeis.clear(Event.class);
        }
        taupulate = false;
    }

    public void applyFilter(NamedFilter filter, boolean visible) {
        filter.setVisibility(visible);
        if(visible) {
            SeismogramDisplay.getActiveFilters().add(filter);
        } else {
            SeismogramDisplay.getActiveFilters().remove(filter);
        }
        DisplayUtils.applyFilter(filter,
                                 getDisplay().iterator(DrawableSeismogram.class));
    }

    public void setOriginal(boolean visible) {
        SeismogramDisplay disp = getDisplay();
        if(disp != null) {
            Iterator it = disp.iterator(DrawableSeismogram.class);
            while(it.hasNext()) {
                ((DrawableSeismogram)it.next()).setVisibility(visible);
            }
        }
    }

    public void refreshDisplays() {}

    public void clearSelections() {
        if(getDisplay() instanceof VerticalSeismogramDisplay) {
            ((VerticalSeismogramDisplay)getDisplay()).clearSelections();
        }
    }

    public void setDisplayMaker(DisplayMakerTask task) {
        if(careAboutLackOfEventForDisplay == true && displayMaker != null
                && task.needsEventInformation()
                && !allHaveDistances(display.getSeismograms())) {
            if(JOptionPane.showConfirmDialog(this,
                                             "You are attempting to switch to a display that requires event information with some seismograms that don't have event information.\nIf you click yes, some traces will not display.  Do you want to do this?",
                                             "Some seismograms won't display in this mode",
                                             JOptionPane.YES_NO_OPTION,
                                             JOptionPane.QUESTION_MESSAGE) == JOptionPane.NO_OPTION) {
                return;
            }
            careAboutLackOfEventForDisplay = false;
        }
        displayMaker = task;
        setDisplay();
    }

    public boolean allHaveDistances(DataSetSeismogram[] seismograms) {
        for(int i = 0; i < seismograms.length; i++) {
            if(!hasDistance(seismograms[i])) {
                return false;
            }
        }
        return true;
    }

    public boolean hasDistance(DataSetSeismogram seismo) {
        return DisplayUtils.calculateDistance(seismo) != null;
    }

    public void setAmpSetter(AmpSetterTask task) {
        ampSetter = AllTasks.getTaskAction(task);
        task.setAmp(getDisplay());
    }

    public void setTimeSetter(TimeSetterTask task) {
        if(careAboutLackOfEventForTime == true && task.needsEventInformation()
                && !allHaveDistances(display.getSeismograms())) {
            if(JOptionPane.showConfirmDialog(this,
                                             "This mode requires event origin time, which is not available for some/all of your seismograms.\nThese seismograms will be aligned by their first data point.",
                                             "Some seismograms won't display in this mode",
                                             JOptionPane.OK_CANCEL_OPTION,
                                             JOptionPane.QUESTION_MESSAGE) == JOptionPane.CANCEL_OPTION) {
                return;
            }
            careAboutLackOfEventForTime = false;
        }
        timeSetter = AllTasks.getTaskAction(task);
        task.setTime(getDisplay());
    }

    protected void setDisplay() {
        SeismogramDisplay newDisplay = displayMaker.makeDisplay();
        if(timeSetter != null) {
            getTimeSetter().setTime(newDisplay);
        }
        if(display != null && display.getSeismograms().length > 0) {
            newDisplay.setTimeConfig(display.getTimeConfig());
            newDisplay.setAmpConfig(display.getAmpConfig());
            DataSetSeismogram[] seis = display.getSeismograms();
            ListIterator it = orderedSeis.listIterator();
            while(it.hasNext()) {
                DataSetSeismogram cur = (DataSetSeismogram)it.next();
                boolean found = false;
                for(int i = 0; i < seis.length; i++) {
                    if(cur.equals(seis[i])) {
                        found = true;
                    }
                }
                if(!found) {
                    it.remove();
                }
            }
            DataSetSeismogram[] orderedExistantSeis = new DataSetSeismogram[orderedSeis.size()];
            orderedSeis.toArray(orderedExistantSeis);
            newDisplay.add(orderedExistantSeis);
            if(taupulate) {
                displayTravelTimes(newDisplay);
            }
            display.clear();
        }
        if(ampSetter != null) {
            getAmpSetter().setAmp(newDisplay);
        }
        if(addDisplay) {
            removeAll();
            add(scrollPane);
            if(displayMaker instanceof RecordSectionTask) {
                add(((RecordSectionTask)displayMaker).getSliderPanel(),
                    BorderLayout.EAST);
            }
        }
        JViewport scrollViewport = scrollPane.getViewport();
        scrollViewport.removeAll();
        scrollViewport.add(newDisplay);
        display = newDisplay;
        boolean recSecDisplay = display instanceof RecordSectionDisplay;
        if(rangeSelTA != null) {
            rangeSelTA.setEnabled(!recSecDisplay);
        }
        if(customPickTA != null) {
            customPickTA.setEnabled(!recSecDisplay);
        }
        revalidate();
    }

    public SeismogramDisplay getDisplay() {
        if(display == null) {
            setDisplay();
        }
        return display;
    }

    public void makeSeismogramDisplayVisible() {
        if(!addDisplay) {
            getDisplay();
            addDisplay = true;
            removeAll();
            add(scrollPane);
            if(displayMaker instanceof RecordSectionTask) {
                add(((RecordSectionTask)displayMaker).getSliderPanel(),
                    BorderLayout.EAST);
            }
            revalidate();
        }
    }

    private AmpSetterTask getAmpSetter() {
        try {
            return (AmpSetterTask)ampSetter.getTask();
        } catch(ConfigurationException e) {
            GlobalExceptionHandler.handle(e);
        }
        return null;
    }

    private TimeSetterTask getTimeSetter() {
        try {
            return (TimeSetterTask)timeSetter.getTask();
        } catch(ConfigurationException e) {
            GlobalExceptionHandler.handle(e);
        }
        return null;
    }

    private LinkedList orderedSeis = new LinkedList();

    private boolean addDisplay = false;

    private DisplayMakerTask displayMaker;

    private TaskAction safeDisplayMakerTA;

    private TaskAction defaultDisplayMakerTA;

    private TimeSetterTask safeTimeSetter;

    private TaskAction ampSetter;

    private TaskAction timeSetter;

    private TaskAction rangeSelTA, customPickTA;

    private JScrollPane scrollPane = new JScrollPane();

    private boolean taupulate = false;

    protected Map params;

    protected SeismogramDisplay display;

    private TimeAmpLabelTask timeAmpLabelTask;

    private TaskAction taupTaskAction;

    private PlaceholderImage placeholder;

    private boolean careAboutLackOfEventForTime = true,
            careAboutLackOfEventForDisplay = true;

    private static Category logger = Category.getInstance(ColumnSeismogramTask.class.getName());
}// ColumnSeismogramTask
