package edu.sc.seis.gee.task;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

import org.apache.log4j.Category;

import edu.iris.Fissures.AuditInfo;
import edu.iris.Fissures.Location;
import edu.iris.Fissures.IfEvent.EventAccessOperations;
import edu.iris.Fissures.IfEvent.Origin;
import edu.iris.Fissures.IfNetwork.Channel;
import edu.iris.Fissures.IfSeismogramDC.RequestFilter;
import edu.iris.Fissures.model.MicroSecondDate;
import edu.iris.Fissures.model.QuantityImpl;
import edu.iris.Fissures.model.TimeInterval;
import edu.iris.Fissures.model.UnitImpl;
import edu.iris.Fissures.network.ChannelIdUtil;
import edu.sc.seis.TauP.Arrival;
import edu.sc.seis.TauP.SphericalCoords;
import edu.sc.seis.fissuresUtil.cache.AbstractJob;
import edu.sc.seis.fissuresUtil.cache.CacheEvent;
import edu.sc.seis.fissuresUtil.cache.JobTracker;
import edu.sc.seis.fissuresUtil.cache.WorkerThreadPool;
import edu.sc.seis.fissuresUtil.chooser.IntervalChooser;
import edu.sc.seis.fissuresUtil.chooser.IntervalChooserOptions;
import edu.sc.seis.fissuresUtil.dataset.Organizer;
import edu.sc.seis.fissuresUtil.exceptionHandler.GlobalExceptionHandler;
import edu.sc.seis.fissuresUtil.xml.DataSetSeismogram;
import edu.sc.seis.gee.CommonAccess;
import edu.sc.seis.gee.NoNetworkException;
import edu.sc.seis.gee.configurator.ConfigurationException;

/**
 * EventSeismogramTask.java
 *
 *
 * Created: Wed Jan 16 13:05:50 2002
 *
 * @author <a href="mailto:telukutl@piglet">Srinivasa Telukutla</a>
 * @version $Id: EventSeismogramTask.java 19842 2008-08-19 20:38:05Z crotwell $
 */

public class EventSeismogramTask extends JPanel implements Task {
    /**
     * Describe <code>invoke</code> method here.
     *
     * @exception Exception if an error occurs
     */
    public void invoke() throws Exception{
        if (! guiInitialized) {
            guiInitialized = true;
            createGUI();
        }
    }

    /**
     * Describe <code>configure</code> method here.
     *
     * @param params a <code>java.util.Map</code> value
     * @exception ConfigurationException if an error occurs
     */
    public void configure(Map params) throws ConfigurationException {
        configParams = params;
        CommonAccess commonAccess = CommonAccess.getCommonAccess();

        String taskId = (String)params.get("showEventsTask");
        TaskAction taskAction =
            commonAccess.getTaskAction(taskId);
        showEventsTask = (ShowEventsTask)taskAction.getTask();

        taskId = (String)params.get("loadSeismogramTask");
        taskAction = commonAccess.getTaskAction(taskId);
        loadSeismogramTask = (LoadSeismogram)taskAction.getTask();

        taskId = (String)params.get("channelChooserTask");
        taskAction =
            commonAccess.getTaskAction(taskId);
        channelChooserTask = (ChannelChooserTask)taskAction.getTask();

        if (showEventsTask == null) {
            throw new ConfigurationException("Invalid configuration, showEventsTask must not be null");
        }
        if (loadSeismogramTask == null) {
            throw new ConfigurationException("Invalid configuration, loadSeismogramTask must not be null");
        }
        if (channelChooserTask == null) {
            throw new ConfigurationException("Invalid configuration, channelChooserTask must not be null");
        }
    }

    public void destroy(){}



    private void createGUI() {
        final GridBagLayout bagLayout = new GridBagLayout();
        final GridBagConstraints constraints = new GridBagConstraints();
        final JButton okButton = new JButton("Load Seismograms");
        JPanel endPanel = new JPanel();
        endPanel.setLayout(new FlowLayout());
        endPanel.add(okButton);

        JLabel beginLabel = new JLabel("Begin");
        JLabel endLabel = new JLabel("End");
        JLabel phaseLabel = new JLabel("Phase");
        JLabel offsetLabel = new JLabel("offSet");
        beginPhase = new JComboBox();
        endPhase = new JComboBox();
        //final JTextField beginOffSet = new JTextField("0");
        //final JTextField endOffSet = new JTextField("600");
        IntervalChooserOptions[] beginOffsetOptions = new IntervalChooserOptions[2];
        beginOffsetOptions[0] = IntervalChooserOptions.SECOND;
        beginOffsetOptions[1] = IntervalChooserOptions.MINUTE;


        IntervalChooserOptions[] endOffsetOptions = new IntervalChooserOptions[2];
        endOffsetOptions[0] = IntervalChooserOptions.SECOND;
        endOffsetOptions[1] = IntervalChooserOptions.MINUTE;

        beginOffSet = new IntervalChooser(beginOffsetOptions);
        endOffSet = new IntervalChooser(endOffsetOptions);

        //set Editable to true and set the Default values for the Offsets
        beginOffSet.setEditable(true);
        endOffSet.setEditable(true);
        beginOffSet.setSelectedValue(-2);
        endOffSet.setSelectedValue(10);

        //beginOffSet.setDefault(9, beginOffsetOptions[1]);
        //endOffSet.setDefault(29, endOffsetOptions[1]);

        //populate the phases and set Default Values.
        populatePhases(beginPhase);
        populatePhases(endPhase);
        beginPhase.setSelectedItem(firstPPhase);
        endPhase.setSelectedItem(firstSPhase);

        constraints.weighty = 1.0;
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.fill = constraints.HORIZONTAL;

        this.setLayout(bagLayout);

        constraints.gridx++;
        this.add(phaseLabel, constraints);

        constraints.gridx++;
        bagLayout.setConstraints(offsetLabel, constraints);
        this.add(offsetLabel, constraints);

        constraints.gridx = 0;
        constraints.gridy++;
        this.add(beginLabel, constraints);

        constraints.gridx++;
        this.add(beginPhase, constraints);

        constraints.gridx++;
        this.add(beginOffSet, constraints);

        constraints.gridx = 0;
        constraints.gridy++;
        this.add(endLabel, constraints);

        constraints.gridx++;
        this.add(endPhase, constraints);

        constraints.gridx++;
        this.add(endOffSet, constraints);

        constraints.gridy++;
        constraints.gridx = 0;
        constraints.gridwidth = GridBagConstraints.REMAINDER;
        this.add(endPanel, constraints);

        constraints.gridy++;
        this.add(progressBar, constraints);

        okButton.addActionListener(new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        getSeismograms();
                    }
                });
    } // end createGUI

    protected void getSeismograms() {
        if (progressOwner != null) {
            return;
        } // end of if (progressOwner != null)

        // check to make sure event have been selected
        EventAccessOperations[] eventAccess =
            showEventsTask.getSelectedEvents();
        if(eventAccess == null || eventAccess.length == 0) {
            JOptionPane.showMessageDialog(null,
                                          "No Events Selected. Select Events First From the Task Select Events",
                                          "No Selected Events",
                                          JOptionPane.INFORMATION_MESSAGE);
        }

        String bPhase = (String)beginPhase.getSelectedItem();
        String ePhase = (String)endPhase.getSelectedItem();
        TimeInterval beginInterval = beginOffSet.getInterval();
        TimeInterval endInterval = endOffSet.getInterval();

        seisLoader.invokeLater(new SeismogramLoader(eventAccess,
                                                    bPhase,
                                                    beginInterval,
                                                    ePhase,
                                                    endInterval,
                                                    taup));
    }

    private void populatePhases(JComboBox comboBox) {

        String[] phases = {originPhase,
                firstPPhase,
                firstSPhase
        };
        for (int i=0; i<phases.length; i++) {
            comboBox.addItem(phases[i]);
        } // end of for (int i=0; i<phases.length; i++)
    }

    /**
     * Describe <code>populateLocalDataSet</code> method here.
     *
     * @param cacheEvent a <code>CacheEvent</code> value
     * @param seismograms a <code>LocalSeismogram[]</code> value
     */
    public void populateLocalDataSet(EventAccessOperations event, DataSetSeismogram seismogram, Channel channel) {

        Organizer organizer = CommonAccess.getCommonAccess().getOrganizer();
        AuditInfo[] audit = new AuditInfo[1];
        audit[0] = new AuditInfo(System.getProperty("user.name", "GEE"),
                                 "loaded from EventSeismogram Task");
        organizer.addSeismogram(seismogram, event, audit);
        organizer.addChannel(channel, event, audit);

    }

    /**
     * Describe <code>populateLocalDataSet</code> method here.
     *
     * @param cacheEvent a <code>CacheEvent</code> value
     * @param channels a <code>Channel[]</code> value
     */
    public void populateLocalDataSet(CacheEvent cacheEvent, Channel[] channels,
                                     edu.iris.Fissures.Time start_time,
                                     edu.iris.Fissures.Time end_time)
        throws ConfigurationException,
        org.omg.CosNaming.NamingContextPackage.NotFound ,
        org.omg.CosNaming.NamingContextPackage.CannotProceed,
        org.omg.CosNaming.NamingContextPackage.InvalidName,
        org.omg.CORBA.ORBPackage.InvalidName,
        NoNetworkException {


        Organizer organizer = CommonAccess.getCommonAccess().getOrganizer();
        for(int counter = 0; counter < channels.length; counter++) {
            RequestFilter rf = new RequestFilter(channels[counter].get_id(),
                                                 start_time,
                                                 end_time);
            DataSetSeismogram dss = loadSeismogramTask.retrieveSeismogram(rf);

            if ( dss == null) {
                continue;
            } // end of if ()


            // this is temporary
            dss.setName("DSS_"+ChannelIdUtil.toStringNoDates(channels[counter].get_id()));

            AuditInfo[] audit = new AuditInfo[1];
            audit[0] =
                new AuditInfo(System.getProperty("user.name"),
                              "loaded channel.");
            organizer.addChannel(channels[counter], cacheEvent, audit);

            audit = new AuditInfo[1];
            audit[0] =
                new AuditInfo(System.getProperty("user.name"),
                              "loaded seismogram for event.");

            organizer.addSeismogram(dss, cacheEvent, audit);
        }
    }

    protected void loadSeismograms(EventAccessOperations[] eventAccess,
                                   String bPhase,
                                   double timeBeforeBPhase,
                                   String ePhase,
                                   double timeAfterEPhase,
                                   DisplayAllTask display){

        TimeInterval beginInterval = new TimeInterval(timeBeforeBPhase, UnitImpl.MINUTE);
        TimeInterval endInterval = new TimeInterval(timeAfterEPhase, UnitImpl.MINUTE);

        seisLoader.invokeLater(new SeismogramLoader(eventAccess,
                                                    bPhase,
                                                    beginInterval,
                                                    ePhase,
                                                    endInterval,
                                                    taup,
                                                    display));
    }

    protected void loadSeismograms(EventAccessOperations[] eventAccess,
                                   String bPhase,
                                   double timeBeforeBPhase,
                                   String ePhase,
                                   double timeAfterEPhase){

        TimeInterval beginInterval = new TimeInterval(timeBeforeBPhase, UnitImpl.MINUTE);
        TimeInterval endInterval = new TimeInterval(timeAfterEPhase, UnitImpl.MINUTE);

        seisLoader.invokeLater(new SeismogramLoader(eventAccess,
                                                    bPhase,
                                                    beginInterval,
                                                    ePhase,
                                                    endInterval,
                                                    taup));
    }


    /**
     * sets this thread as the owner of the progress bar. It is the only
     thread that can update the progress bar. Also resets the value to 0;
     * @param t a <code>Thread</code> value
     */
    protected synchronized void setProgressOwner(Thread t) {
        progressOwner = t;
    }

    /**
     * Describe <code>setProgressValue</code> method here.
     *
     * @param t a <code>Thread</code> value
     * @param value an <code>int</code> value
     */
    protected synchronized void setProgressValue(Thread t, int value) {
        if (t.equals(progressOwner)) {
            progressBar.setValue(value);
        }
    }

    /**
     * Describe <code>setProgressMax</code> method here.
     *
     * @param t a <code>Thread</code> value
     * @param max an <code>int</code> value
     */
    protected synchronized void setProgressMax(Thread t, int max) {
        if (t.equals(progressOwner)) {
            progressBar.setMaximum(max);
        }
    }

    private String originPhase = "Origin Time";

    private String firstPPhase = "First P-wave";

    private String firstSPhase = "First S-wave";

    private IntervalChooser beginOffSet;

    private IntervalChooser endOffSet;

    private JComboBox beginPhase;

    private JComboBox endPhase;

    private boolean guiInitialized = false;

    protected Map configParams;

    private ShowEventsTask showEventsTask;

    private LoadSeismogram loadSeismogramTask;

    protected ChannelChooserTask channelChooserTask;

    private JProgressBar progressBar = new JProgressBar();
    private Thread progressOwner = null;

    private TauP taup = new TauP("");

    protected class SeismogramLoader extends AbstractJob {
        SeismogramLoader(EventAccessOperations[] eventAccess,
                         String bPhase,
                         TimeInterval beginInterval,
                         String ePhase,
                         TimeInterval endInterval,
                         TauP taup) {
            super("Seismogram Loader");
            JobTracker.getTracker().add(this);
            this.eventAccess = eventAccess;
            this.bPhase = bPhase;
            this.beginInterval = beginInterval;
            this.ePhase = ePhase;
            this.endInterval = endInterval;
            this.taup = taup;
        }

        SeismogramLoader(EventAccessOperations[] eventAccess,
                         String bPhase,
                         TimeInterval beginInterval,
                         String ePhase,
                         TimeInterval endInterval,
                         TauP taup,
                         DisplayAllTask display) {
            this(eventAccess,
                 bPhase,
                 beginInterval,
                 ePhase,
                 endInterval,
                 taup);
            this.display = display;
        }

        EventAccessOperations[] eventAccess;
        String bPhase;
        TimeInterval beginInterval;
        String ePhase;
        TimeInterval endInterval;
        private TauP taup;
        private DisplayAllTask display;

        /**
         * Describe <code>run</code> method here.
         *
         */
        public void runJob() {
            setStatus("Determining phase");
            if (bPhase.equals(firstPPhase)) {
                bPhase = "ttp";
            }
            if (ePhase.equals(firstPPhase)) {
                ePhase = "ttp";
            }
            if (bPhase.equals(firstSPhase)) {
                bPhase = "tts";
            }
            if (ePhase.equals(firstSPhase)) {
                ePhase = "tts";
            }

            setProgressOwner(Thread.currentThread());
            setProgressValue(Thread.currentThread(), 0);
            setStatus("Getting channels");
            int numSeismograms = 0;
            Origin origin = null;
            MicroSecondDate originTime;
            Channel[] channels;

            for (int eventNum=0; eventNum<eventAccess.length; eventNum++) {
                try {
                    origin = eventAccess[eventNum].get_preferred_origin();
                    originTime = new MicroSecondDate(origin.getOriginTime());
                    channels = channelChooserTask.getChannelChooser().getSelectedChannels(originTime);

                    if(channels.length == 0){
                        String mesg = (String)configParams.get("NoStationWithDataMessage");
                        if (mesg == null) {
                            mesg = "Either no stations were selected or the selected stations are not active for this earthquake";
                        }
                        JOptionPane.showMessageDialog(null, mesg,
                                                      "No stations selected", JOptionPane.WARNING_MESSAGE);
                        setFinished();
                        return;
                    }

                    logger.debug("The beginPhase is "+bPhase);
                    logger.debug("The endPhase is "+ePhase);
                    logger.debug("The depth is "+origin.getLocation().depth.value);
                    logger.debug("Num Channel->"+channels.length);
                    for(int channelNum = 0; channelNum < channels.length; channelNum++){
                        String channelStatus = (channelNum + 1) + "/" + channels.length;
                        setStatus("Loading channel " + channelStatus);// + " for event " + eventStatus);
                        //debugChannelNum = counter;
                        //logger.debug("Channel->"+ChannelIdUtil.toStringNoDates(channels[counter].get_id()));
                        MicroSecondDate startTime;
                        if (!bPhase.equals(originPhase)) {
                            String[] phase = {bPhase};
                            Arrival[] beginArrivals = getArrivals(origin.getLocation(),
                                                                  channels[channelNum].getSite().getLocation(),
                                                                  phase,
                                                                  taup);
                            if (beginArrivals.length == 0) {
                                // no arrivals at this station
                                logger.debug("No arrivals for begin at this station "+ChannelIdUtil.toString(channels[channelNum].get_id()));
                                continue;
                            } // end of if (beginArrivals.length == 0)
                            startTime = originTime.add(new TimeInterval(beginArrivals[0].getTime(), UnitImpl.SECOND));
                        } else {
                            startTime = new MicroSecondDate(originTime);
                        } // end of else
                        startTime = startTime.add(beginInterval);

                        MicroSecondDate endTime;
                        if (!ePhase.equals(originPhase)) {
                            String[] phase = {ePhase};
                            Arrival[] endArrivals = getArrivals(origin.getLocation(),
                                                                channels[channelNum].getSite().getLocation(),
                                                                phase,
                                                                taup);
                            if (endArrivals.length == 0) {
                                // no arrivals for this station
                                logger.debug("No arrivals for begin at this station "+ChannelIdUtil.toString(channels[channelNum].get_id()));
                                continue;
                            } // end of if (endArrivals.length == 0)
                            endTime = originTime.add(new TimeInterval(endArrivals[0].getTime(), UnitImpl.SECOND));
                        } else {
                            endTime = new MicroSecondDate(originTime);
                        } // end of else

                        endTime = endTime.add(endInterval);

                        logger.debug("Before loadSeismogram call "+ChannelIdUtil.toString(channels[channelNum].get_id()));
                        setStatus("Loading seismogram for channel " + channelStatus);// + " for event " + eventStatus);
                        DataSetSeismogram seismogram =
                            loadSeismogramTask.retrieveSeismogram(startTime.getFissuresTime(),
                                                                  endTime.getFissuresTime(),
                                                                  channels[channelNum].get_id());
                        logger.debug("After loadSeismogram call "+ChannelIdUtil.toString(channels[channelNum].get_id()));
                        setProgressValue(Thread.currentThread(),
                                         2*(eventNum*channels.length + channelNum)+2);

                        if (seismogram != null) {
                            populateLocalDataSet(eventAccess[eventNum], seismogram, channels[channelNum]);
                            if (display != null){
                                display.display(seismogram);
                            }
                            numSeismograms++;
                        } else {
                            logger.info("No available data for "+
                                            ChannelIdUtil.toString(channels[channelNum].get_id()));
                        }
                        setProgressValue(Thread.currentThread(),
                                         2*(eventNum*channels.length + channelNum)+3);

                    }
                } catch(Exception ex) {
                    GlobalExceptionHandler.handle("Problem trying to get seismograms.",
                                                 ex);
                }
            } // end of for (eventNum=0; eventNum<eventAccess.length; eventNum++)
            setFinished();
            setProgressValue(Thread.currentThread(), progressBar.getMaximum());
            setProgressOwner(null);
        }
    }

    public static Arrival[] getArrivals(Location event, Location here,
                                        String[] phases, TauP taup){
        double distance = SphericalCoords.distance(here.latitude, here.longitude,
                                                   event.latitude, event.longitude);
        double depth = ((QuantityImpl)event.depth).convertTo(UnitImpl.KILOMETER).value;
        return taup.calculate(distance, depth, phases);
    }

    protected WorkerThreadPool seisLoader = new WorkerThreadPool("Event Seismogram Loader", 2);

    static Category logger = Category.getInstance(EventSeismogramTask.class.getName());


}// EventSeismogramTask





