package edu.sc.seis.gee;

import java.awt.BorderLayout;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.xml.sax.InputSource;

import edu.iris.Fissures.AuditInfo;
import edu.iris.Fissures.model.AllVTFactory;
import edu.sc.seis.fissuresUtil.bag.TauPUtil;
import edu.sc.seis.fissuresUtil.cache.AbstractJob;
import edu.sc.seis.fissuresUtil.cache.JobTracker;
import edu.sc.seis.fissuresUtil.cache.WorkerThreadPool;
import edu.sc.seis.fissuresUtil.database.ConnMgr;
import edu.sc.seis.fissuresUtil.dataset.DataSetEventOrganizer;
import edu.sc.seis.fissuresUtil.dataset.Organizer;
import edu.sc.seis.fissuresUtil.exceptionHandler.FilterReporter;
import edu.sc.seis.fissuresUtil.exceptionHandler.GUIReporter;
import edu.sc.seis.fissuresUtil.exceptionHandler.GlobalExceptionHandler;
import edu.sc.seis.fissuresUtil.exceptionHandler.ServletReporter;
import edu.sc.seis.fissuresUtil.namingService.FissuresNamingService;
import edu.sc.seis.fissuresUtil.netConnChecker.Checker;
import edu.sc.seis.fissuresUtil.netConnChecker.ConnChecker;
import edu.sc.seis.fissuresUtil.netConnChecker.ConnStatus;
import edu.sc.seis.fissuresUtil.netConnChecker.HTTPChecker;
import edu.sc.seis.fissuresUtil.netConnChecker.RootCorbaChecker;
import edu.sc.seis.fissuresUtil.xml.DataSet;
import edu.sc.seis.fissuresUtil.xml.MemoryDataSet;
import edu.sc.seis.gee.configurator.ConfigurationException;
import edu.sc.seis.gee.configurator.Configure;
import edu.sc.seis.gee.configurator.DefaultParamNames;
import edu.sc.seis.gee.task.AllTasks;
import edu.sc.seis.gee.task.StatusIndicator;
import edu.sc.seis.gee.task.Task;
import edu.sc.seis.gee.task.TaskAction;
import edu.sc.seis.gee.task.VersionTask;
import edu.sc.seis.sod.Args;
import edu.sc.seis.sod.Start;

/**
 * CommonAccess.java Created: Thu Dec 20 17:18:09 2001
 * 
 * @author Philip Crotwell
 * @version $Id: CommonAccess.java 19019 2007-04-29 23:50:16Z crotwell $
 */
public class CommonAccess {

    private CommonAccess() {
        // bootstrap sod database for things like QueryEvents that use SOD sources
        try {
        Properties props = new Properties();
        props.put("fissuresUtil.database.url", "jdbc:hsqldb:mem:SodDB");
        edu.sc.seis.sod.Start s = new edu.sc.seis.sod.Start(new Args(new String[] {"-f", "<stream>","-q"}),
                            new Start.InputSourceCreator() {

                                public InputSource create() {
                                    return new InputSource(new StringReader("<sod></sod>\n"));
                                }
                            },
                            props,
                            true);
        s.setupDatabaseForUnitTests();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** The singleton, no more than one of this object type per process. */
    private static CommonAccess singleton = new CommonAccess();

    public static CommonAccess getCommonAccess() {
        return singleton;
    }

    public void resetActivity(String id) throws ConfigurationException {
        resetActivity(id, true);
    }

    public void resetActivity(String id, boolean inNewThread)
            throws ConfigurationException {
        getConfigure().setActivity(id);
        resetActivity(inNewThread);
    }

    private class Resetter extends AbstractJob {

        public Resetter() {
            super("Activity Changer");
            setFinished();
            StatusIndicator indicator = new StatusIndicator("Switching activities");
            window.getContentPane().setLayout(new BorderLayout());
            indicator.setBorder(BorderFactory.createRaisedBevelBorder());
            window.getContentPane().add(indicator, BorderLayout.CENTER);
            window.pack();
            window.setLocation(FrameManager.getLoc(DefaultParamNames.JUSTIFY_CENTER,
                                                   window.getSize()));
            window.pack();
        }

        private JWindow window = new JWindow();

        public void runJob() {
            try {
                reset();
            } catch(ConfigurationException e) {
                GlobalExceptionHandler.handle(e);
            }
        }

        public void reset() throws ConfigurationException {
            setStatus("Changing activity");
            JobTracker.getTracker().clearFinished();
            JFrame frame = FrameManager.getManager().reset();
            window.setVisible(true);
            clearDataSetRoot();
            AllTasks.resetAll();
            Configure config = getConfigure();
            String[] initialTasks = config.getInitialTasks();
            for(int i = 0; i < initialTasks.length; i++) {
                logger.debug("Initial task: " + initialTasks[i]);
                TaskAction task = getTaskAction(initialTasks[i]);
                if(task != null) {
                    task.actionPerformed(new java.awt.event.ActionEvent(this,
                                                                        i,
                                                                        "setSize"));
                } // end of if (task != null)
            } // end of for (int i=0; i<initialTasks.length; i++)
            String[] inactiveTasks = config.getInactiveTasks();
            for(int i = 0; i < inactiveTasks.length; i++) {
                logger.debug("Inactive task: " + inactiveTasks[i]);
                TaskAction task = getTaskAction(inactiveTasks[i]);
                if(task != null) {
                    task.setEnabled(false);
                } // end of if (task != null)
            } // end of for (int i=0; i<initialTasks.length; i++)
            window.setVisible(false);
            frame.setVisible(true);
            setFinished();
        }
    }

    private Resetter resetter = new Resetter();

    private WorkerThreadPool resetPool = new WorkerThreadPool("Reset Pool", 1);

    public void resetActivity() throws ConfigurationException {
        resetActivity(true);
    }

    /** resets the activity to the current/initial activity. */
    public void resetActivity(boolean inNewThread)
            throws ConfigurationException {
        JobTracker.getTracker().add(resetter);
        if(inNewThread)
            resetPool.invokeLater(resetter);
        else
            resetter.reset();
    }

    public JFrame getMainFrame() {
        return FrameManager.getManager().getCurrentMainFrame();
    }

    public Organizer getOrganizer() {
        if(organizer == null) {
            organizer = new DataSetEventOrganizer(getDataSetRoot());
        } // end of if (organizer == null)
        return organizer;
    }

    /** Sets the top level dataset. */
    public void setDataSetRoot(DataSet newDS) {
        if(newDS == null) {
            throw new RuntimeException("Should call clearDataSetRoot() to null the root dataset");
        }
        rootDataSet = newDS;
    }

    public void clearDataSetRoot() {
        rootDataSet = null;
        organizer = null;
    }

    /** Gets the top level dataset. */
    public DataSet getDataSetRoot() {
        if(rootDataSet == null) {
            edu.iris.Fissures.AuditInfo[] audit_info = new edu.iris.Fissures.AuditInfo[1];
            audit_info[0] = new AuditInfo(System.getProperty("user.name"),
                                          "created in memory.");
            setDataSetRoot(new MemoryDataSet("no id",
                                             "My Data",
                                             "nobody",
                                             audit_info));
        }
        return rootDataSet;
    }

    public org.omg.CORBA_2_3.ORB getORB() throws ConfigurationException {
        if(orb == null) {
            initORB();
        } // end of if (orb == null)
        return orb;
    }

    public TauPUtil getTravelTimeCalc() throws ConfigurationException {
        if(taup == null) {
            taup = TauPUtil.getTauPUtil();
        } // end of if (taup == null)
        return taup;
    }

    private TauPUtil taup = null;

    public FissuresNamingService getFissuresNamingService()
            throws ConfigurationException {
        if(fisName == null) {
            fisName = new FissuresNamingService(getORB());
            if(props.containsKey(FissuresNamingService.CORBALOC_PROP)) {
                fisName.setNameServiceCorbaLoc((String)props.get(FissuresNamingService.CORBALOC_PROP));
            } // end of if ()
        } // end of if (fisName == null)
        return fisName;
    }

    private DataSet rootDataSet = null;

    public TaskAction getTaskAction(String taskId) {
        return AllTasks.getTaskAction(taskId);
    }

    public TaskAction getTaskAction(Task t) {
        return AllTasks.getTaskAction(t);
    }

    public JPanel getDisplayArea() {
        return FrameManager.getManager().getCurrentPanel();
    }

    public Configure getConfigure() {
        return configure;
    }

    private Configure configure = new Configure();

    protected void init(String[] args) throws ConfigurationException {
        this.args = args;
        List ignoreList = new ArrayList();
        // silently eat NoNetworkException as the user has already been notified
        ignoreList.add(NoNetworkException.class);
        GlobalExceptionHandler.add(new FilterReporter(new GUIReporter(),
                                                      ignoreList));
        if(System.getProperty("errorHandlerServlet") != null) {
            GlobalExceptionHandler.add(new ServletReporter());
        }
        GlobalExceptionHandler.add(new NetworkGateKeeper.SystemExceptionInterceptor());
        props = System.getProperties();
        String config = "edu/sc/seis/gee/data/conf/DefaultConfig.xml";
        for(int i = 0; i < args.length - 1; i++) {
            if(args[i].equals("-props") || args[i].equals("-p")) {
                // override with values in local directory,
                // but still load defaults with original name
                String prop = args[i + 1];
                try {
                    FileInputStream in = new FileInputStream(prop);
                    props.load(in);
                    in.close();
                } catch(FileNotFoundException f) {
                    // logger.warn
                    System.err.println(" file missing " + f + " using defaults");
                } catch(IOException f) {
                    // logger.warn
                    System.err.println(f.toString() + " using defaults");
                }
            } else if(args[i].equals("-config")) {
                config = args[i + 1];
            } else if(args[i].equals("-activity")) {
                initialActivity = args[i + 1];
            }
        }
        props.put("edu.sc.seis.gee.configuration", config);
        // configure logging from properties...
        if(props.get("log4j.rootCategory") == null) {
            props.put("log4j.threshold", "OFF");
        } // end of if (props.get("log4j.root") == null)
        if(props.get("log4j.appender.R.File") == null) {
            props.put("log4j.appender.R.File", getCacheDirectory() + "/GEE.log");
            // make sure directory exists for log files before creating
            File logFile = new File((String)props.get("log4j.appender.R.File"));
            logFile.getParentFile().mkdirs();
        }
        GlobalExceptionHandler.add("Logger Output",
                                   new File((String)props.get("log4j.appender.R.File")));
        PropertyConfigurator.configure(props);
        logger.info("Logging configured, logging to "+props.get("log4j.appender.R.File"));
        logger.info("Configuration: "+config);
        java.net.Authenticator.setDefault(new SimpleAuthenticator());
        initializeConnChecker();
    }

    private void initializeConnChecker() throws ConfigurationException {
        connCheckers = new Checker();
        connCheckers.add(new HTTPChecker("Connection to public web site",
                                         "http://www.yahoo.com"));
        connCheckers.add(new HTTPChecker("Connection to IRIS web site",
                                         "http://www.iris.edu"));
        connCheckers.add(new HTTPChecker("Connection to IRIS web services",
                "http://service.iris.edu/fdsnws/event/1/query?eventid=3954686"));
    }

    public static String getCacheDirectory() {
        if(cacheDirectory == null) {
            initCacheDirectory();
        }
        return cacheDirectory;
    }

    protected synchronized static void initCacheDirectory() {
        try {
            File cacheDir = new File(cacheDirectoryString);
            cacheDir.mkdirs();
            File versionFile = new File(cacheDir, "GEE_version");
            if(versionFile.exists()) {
                BufferedReader in = new BufferedReader(new FileReader(versionFile));
                String fileVersion = in.readLine();
                if(!fileVersion.equals(VersionTask.getVersion())) {
                    // version is old, clean up and re-init
                    cleanCache();
                }
            } else {
                cleanCache();
            }
        } catch(IOException e) {
            // oh well, log it and hope for the best
            logger.error("unable to clean/create chache directory "
                    + cacheDirectoryString, e);
        }
        cacheDirectory = cacheDirectoryString;
    }

    protected static void cleanCache() throws IOException {
        File cacheDir = new File(cacheDirectoryString);
        deleteDirectory(cacheDir);
        cacheDir.mkdirs();
        File versionFile = new File(cacheDirectoryString, "GEE_version");
        BufferedWriter versionOut = new BufferedWriter(new FileWriter(versionFile));
        versionOut.write(VersionTask.getVersion());
        versionOut.close();
    }

    protected static void deleteDirectory(File dir) {
        File[] cacheFiles = dir.listFiles();
        for(int i = 0; i < cacheFiles.length; i++) {
            if(cacheFiles[i].isDirectory()) {
                deleteDirectory(cacheFiles[i]);
            }
            cacheFiles[i].delete();
        }
        dir.delete();
    }

    protected static String cacheDirectory = null;

    // WARNING : this is also in
    // edu.sc.seis.fissuresUtil.database.AbstractDb.java
    protected static String cacheDirectoryString = System.getProperty("java.io.tmpdir")
            + File.separatorChar
            + "GEE_cache_"
            + System.getProperty("user.name").replaceAll("\\W", "_");

    protected void initORB() throws ConfigurationException {
        if(orb != null) {
            throw new ConfigurationException("CommonAccess already configured");
        }
        // Initialize the ORB.
        orb = (org.omg.CORBA_2_3.ORB)org.omg.CORBA.ORB.init(args, props);
        // register valuetype factories
        AllVTFactory vt = new AllVTFactory();
        vt.register(orb);
        logger.info("initialize " + orb.getClass().toString() + " orb");
    }

    public ConnStatus getNetworkStatus() {
        return connCheckers.getStatus();
    }

    public String getInitialActivity() {
        return initialActivity;
    }

    public void add(ConnChecker checker) {
        connCheckers.add(checker);
    }

    public Checker getChecker() {
        return connCheckers;
    }

    public void appQuit() {
        if (orb != null) {
            orb.shutdown(false);
        }
        System.exit(0);
    }

    private String[] args;

    private Checker connCheckers;

    private Properties props;

    private String initialActivity;

    org.omg.CORBA_2_3.ORB orb = null;

    FissuresNamingService fisName;

    Organizer organizer;

    private static Logger logger = Logger.getLogger(CommonAccess.class);
}// CommonAccess
