package edu.sc.seis.gee.task;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.apache.log4j.Category;
import edu.iris.Fissures.AuditInfo;
import edu.iris.Fissures.IfEvent.EventAccessOperations;
import edu.iris.Fissures.IfNetwork.Channel;
import edu.iris.Fissures.model.MicroSecondDate;
import edu.sc.seis.fissuresUtil.dataset.Organizer;
import edu.sc.seis.fissuresUtil.psn.PSNToFissures;
import edu.sc.seis.fissuresUtil.sac.SacToFissures;
import edu.sc.seis.fissuresUtil.xml.DataSetSeismogram;
import edu.sc.seis.fissuresUtil.xml.SeismogramFileTypes;
import edu.sc.seis.fissuresUtil.xml.URLDataSetSeismogram;
import edu.sc.seis.gee.CommonAccess;
import edu.sc.seis.gee.FrameManager;
import edu.sc.seis.gee.configurator.ConfigurationException;
import edu.sc.seis.seisFile.psn.PSNDataFile;
import edu.sc.seis.seisFile.psn.PSNHeader;
import edu.sc.seis.seisFile.sac.SacConstants;
import edu.sc.seis.seisFile.sac.SacTimeSeries;

/**
 * This pops up a file chooser that lets the user choose a sac file to load into
 * the current dataset. Loading multiple files at once is not yet supported.
 * There are no configurable options for this task. Created: Mon Jan 6 14:43:26
 * 2003
 * 
 * @author <a href="mailto:crotwell@owl.seis.sc.edu">Philip Crotwell </a>
 * @version $Id: LoadSacFile.java 20062 2008-11-19 19:14:20Z crotwell $
 */
public class LoadSacFile implements Task {

    public void invoke() throws ConfigurationException {
        showFileGUI();
    }

    public void destroy() {}

    private void showFileGUI() {
        JFileChooser jfileChooser = new JFileChooser();
        if(lastDir != null) {
            jfileChooser.setCurrentDirectory(lastDir);
        }
        jfileChooser.setDialogTitle((String)configParams.get(TASK_NAME));
        jfileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        jfileChooser.setMultiSelectionEnabled(true);
        CommonAccess commonAccess = CommonAccess.getCommonAccess();
        ChooserRunnable r = new ChooserRunnable(commonAccess, jfileChooser);
        if(!SwingUtilities.isEventDispatchThread()) {
            try {
                SwingUtilities.invokeAndWait(r);
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            r.run();// If we're in the dispatch thread, run the chooser directly
        }
        if(r.chooserStatus == JFileChooser.APPROVE_OPTION) {
            File[] files = jfileChooser.getSelectedFiles();
            // return save parent for return to last dir
            if(!files[0].isDirectory()) {
                lastDir = files[0].getParentFile();
            } else {
                lastDir = files[0];
            } // end of else
            List results = load(files, autodisplay);
            handleLoadResults(results);
        }
    }

    private static void handleLoadResults(List results) {
        if(results.size() == 0) {
            JOptionPane.showMessageDialog(FrameManager.getManager()
                                                  .getCurrentFrame(),
                                          "No files found in the given directory",
                                          "No Files to Load",
                                          JOptionPane.ERROR_MESSAGE);
        } else {
            handleResultsForSomeFound(results);
        }
    }

    private static void handleResultsForSomeFound(List results) {
        List good = new ArrayList();
        List bad = new ArrayList();
        List ugly = new ArrayList();
        Iterator it = results.iterator();
        while(it.hasNext()) {
            FileLoadResult result = (FileLoadResult)it.next();
            if(result.s == SUCCESS) {
                good.add(result);
            } else if(result.s == UNREADABLE_FILE) {
                bad.add(result);
            } else {
                ugly.add(result);
            }
        }
        if(bad.size() > 0 || ugly.size() > 0) {
            String msg = "";
            String title = "";
            if(results.size() == 1) {
                title = "Unable to Load File";
                if(ugly.size() > 0) {
                    msg = "GEE is only able to read SAC, PSN and MSEED.";
                } else {
                    msg = "GEE was unable to read the given file.";
                }
            } else {
                if(good.size() > 0) {
                    title = "Unable to Load Some Files";
                    msg = "Some files were loaded successfully by GEE.\n";
                } else {
                    title = "Unable to Load Files";
                }
                if(ugly.size() > 0) {
                    msg += "There were some files that weren't SAC, PSN or MSEED.\n";
                }
                if(bad.size() > 0) {
                    msg += "There were some files that were unreadable.\n";
                }
            }
            JOptionPane.showMessageDialog(FrameManager.getManager()
                    .getCurrentFrame(), msg, title, JOptionPane.ERROR_MESSAGE);
        }
    }

    private final class ChooserRunnable implements Runnable {

        private ChooserRunnable(CommonAccess access, JFileChooser chooser) {
            this.access = access;
            this.chooser = chooser;
        }

        public void run() {
            chooserStatus = chooser.showOpenDialog(access.getMainFrame());
        }

        private CommonAccess access;

        private JFileChooser chooser;

        public int chooserStatus;
    }

    private static class FileLoadResult {

        public FileLoadResult(File f, Status r) {
            this.f = f;
            this.s = r;
        }

        public String toString() {
            return f.getPath() + ": " + s;
        }

        public final File f;

        public final Status s;
    }

    private static class Status {

        public Status(String reason) {
            this.reason = reason;
        }

        public String toString() {
            return reason;
        }

        public final String reason;
    }

    public static final Status SUCCESS = new Status("Successfuly Loaded");

    public static final Status UNREADABLE_FILE = new Status("Unreadable File");

    public static final Status UNKNOWN_FORMAT = new Status("Unknown Format");

    public static List load(File file, boolean autodisplay) {
        return load(new File[] {file}, autodisplay);
    }

    /**
     * @returns a list of FileLoadResult for each file that loaded or had an
     *          attempt to load.
     */
    public static List load(File[] files, boolean autodisplay) {
        List results = new ArrayList();
        load(files, autodisplay, results);
        return results;
    }

    private static void load(File[] files, boolean autodisplay, List results) {
        for(int i = 0; i < files.length; i++) {
            loadFileOrDirectory(files[i], autodisplay, results);
        }
    }

    public static void loadFileOrDirectory(File file,
                                           boolean autodisplay,
                                           List results) {
        if(file.isDirectory()) {
            load(file.listFiles(), autodisplay, results);
            return;
        }
        if(!file.canRead()) {
            results.add(new FileLoadResult(file, UNREADABLE_FILE));
        } else {
            try {
                loadReadableFile(file, autodisplay);
                results.add(new FileLoadResult(file, SUCCESS));
            } catch(IOException e) {
                results.add(new FileLoadResult(file, UNKNOWN_FORMAT));
            }
        }
    }

    private static void loadReadableFile(File file, boolean autodisplay)
            throws IOException {
        if(file.getName().endsWith(".sac")) {
            loadSacFile(file, autodisplay);
        } else if(file.getName().endsWith(".mseed")) {
            loadMSeedFile(file, autodisplay);
        } else {
            loadUnknownExtensionFile(file, autodisplay);
        }
    }

    private static void loadUnknownExtensionFile(File file, boolean autodisplay)
            throws IOException {
        PSNHeader header = getPSNHeader(file.getPath());
        if(header != null) {
            loadPSNFile(file, header.getNumRecords(), autodisplay);
        } else {
            try {
            loadSacFile(file, autodisplay);
            } catch( IOException e) {
                // not a sac file, try mseed
                loadMSeedFile(file, autodisplay);
            }
        }
    }

    public static void loadMSeedFile(File file, boolean autodisplay)
            throws IOException {
        CommonAccess common = CommonAccess.getCommonAccess();
        AuditInfo[] audit_info = new AuditInfo[1];
        audit_info[0] = new AuditInfo(System.getProperty("user.name"),
                                      "loaded seismogram from mseed file "
                                              + file.getName());
        URLDataSetSeismogram dss = new URLDataSetSeismogram(file.toURL(),
                                                            SeismogramFileTypes.MSEED,
                                                            common.getDataSetRoot());
        Organizer organizer = common.getOrganizer();
        organizer.addSeismogram(dss, audit_info);
        if(autodisplay) {
            addToDisplay(dss);
        }
    }

    public static void loadSacFile(File file, boolean autodisplay)
            throws IOException {
        // System.out.println("loadSacFile called");
        CommonAccess common = CommonAccess.getCommonAccess();
        AuditInfo[] audit_info = new AuditInfo[1];
        audit_info[0] = new AuditInfo(System.getProperty("user.name"),
                                      "loaded seismogram from sac file "
                                              + file.getName());
        URLDataSetSeismogram dss = new URLDataSetSeismogram(file.toURL(),
                                                            SeismogramFileTypes.SAC,
                                                            common.getDataSetRoot());
        SacTimeSeries sac = new SacTimeSeries();
        DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
        sac.read(dis);
        EventAccessOperations event = SacToFissures.getEvent(sac);
        Channel chan = SacToFissures.getChannel(sac);
        Organizer organizer = common.getOrganizer();
        if(event == null) {
            String evdp = "" + sac.getHeader().getEvdp();
            String evla = "" + sac.getHeader().getEvla();
            String evlo = "" + sac.getHeader().getEvlo();
            MicroSecondDate oDate = SacToFissures.getEventOriginTime(sac);
            String o = "" + oDate;
            if(sac.getHeader().getEvdp() == SacConstants.FLOAT_UNDEF) {
                evdp = "Undefined";
            }
            if(sac.getHeader().getEvla() == SacConstants.FLOAT_UNDEF) {
                evla = "Undefined";
            }
            if(sac.getHeader().getEvlo() == SacConstants.FLOAT_UNDEF) {
                evlo = "Undefined";
            }
            if(oDate == null) {
                o = "Undefined";
            }
            // should put up a dialog here with imputs for each of the field and
            // with an option to
            // query the event server for likely event matches, display a list
            // of events with P and/or S
            // within the seismogram window and let the user choose one
            logger.warn("Incomplete event information: EVDP=" + evdp + " EVLA="
                    + evla + " EVLO=" + evlo + " OTime=" + o);
        }
        if(event != null) {
            organizer.addSeismogram(dss, event, audit_info);
            organizer.addChannel(chan, event, audit_info);
        } else {
            organizer.addSeismogram(dss, audit_info);
            organizer.addChannel(chan, audit_info);
        }
        if(autodisplay) {
            addToDisplay(dss);
        }
    }

    public static void loadPSNFile(File file,
                                   int numRecords,
                                   boolean autodisplay) throws IOException {
        CommonAccess commonAccess = CommonAccess.getCommonAccess();
        String fileName = file.getPath();
        PSNDataFile psnData = new PSNDataFile(fileName);
        AuditInfo[] audit_info = new AuditInfo[1];
        audit_info[0] = new AuditInfo(System.getProperty("user.name"),
                                      "loaded seismogram from data file "
                                              + fileName);
        for(int i = 0; i < numRecords; i++) {
            URLDataSetSeismogram dss = new URLDataSetSeismogram(URLDataSetSeismogram.createPSNURL(file.toURL(),
                                                                                                  i),
                                                                SeismogramFileTypes.PSN,
                                                                commonAccess.getDataSetRoot());
            Organizer organizer = commonAccess.getOrganizer();
            EventAccessOperations event = PSNToFissures.getEvent(psnData);
            Channel chan = PSNToFissures.getChannel(psnData.getEventRecords()[i]);
            if(event != null) {
                organizer.addSeismogram(dss, event, audit_info);
                organizer.addChannel(chan, event, audit_info);
            } else {
                organizer.addSeismogram(dss, audit_info);
                organizer.addChannel(chan, audit_info);
            }
            if(autodisplay) {
                addToDisplay(dss);
            }
        }
    }

    // this is a crappy little hack to get display to work decently. A task
    // shouldn't really load another task like this
    public static void addToDisplay(DataSetSeismogram seis) {
        addToDisplay(seis, true);
    }

    public static void addToDisplay(DataSetSeismogram seis, boolean bringToFront) {
        TaskAction colSeisTA = CommonAccess.getCommonAccess()
                .getTaskAction("colseisdisplay");
        try {
            ColumnSeismogramTask colSeis = (ColumnSeismogramTask)colSeisTA.getTask();
            colSeis.addSeismogram(seis);
            if(bringToFront)
                colSeisTA.checkDisplayLocation(true);
        } catch(ConfigurationException e) {}
    }

    public static PSNHeader getPSNHeader(String fileName) {
        try {
            DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName)));
            PSNHeader header = new PSNHeader(dis);
            dis.close();
            return header;
        } catch(IOException e) {
            return null;
        }
    }

    public void configure(java.util.Map params) {
        configParams = params;
        if(params.containsKey("autodisplay")) {
            Boolean b = new Boolean((String)params.get("autodisplay"));
            autodisplay = b.booleanValue();
        }
    }

    public static void main(String[] args) {
        handleLoadResults(load(new File[] {new File("classics")}, false));
    }

    boolean autodisplay = true;

    java.io.File lastDir = null;

    java.util.Map configParams;

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