package edu.sc.seis.gee.task;

import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.swing.JPanel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.event.CenterEvent;
import com.bbn.openmap.proj.Projection;

import edu.iris.Fissures.IfEvent.EventAccessOperations;
import edu.iris.Fissures.IfEvent.NoPreferredOrigin;
import edu.iris.Fissures.IfEvent.Origin;
import edu.iris.Fissures.model.MicroSecondDate;
import edu.iris.Fissures.model.TimeInterval;
import edu.iris.Fissures.model.UnitImpl;
import edu.sc.seis.fissuresUtil.cache.AbstractJob;
import edu.sc.seis.fissuresUtil.cache.EventUtil;
import edu.sc.seis.fissuresUtil.cache.JobTracker;
import edu.sc.seis.fissuresUtil.display.EventTableModel;
import edu.sc.seis.fissuresUtil.exceptionHandler.GlobalExceptionHandler;
import edu.sc.seis.fissuresUtil.map.OpenMap;
import edu.sc.seis.fissuresUtil.map.colorizer.event.DepthEventColorizer;
import edu.sc.seis.fissuresUtil.map.layers.ChannelChooserLayer;
import edu.sc.seis.fissuresUtil.map.layers.EventLayer;
import edu.sc.seis.fissuresUtil.map.layers.EventTableLayer;
import edu.sc.seis.fissuresUtil.map.layers.ShapeLayerPropertiesHandler;
import edu.sc.seis.fissuresUtil.map.layers.StationLayer;
import edu.sc.seis.fissuresUtil.map.projections.ProjectionHandler;
import edu.sc.seis.gee.CommonAccess;
import edu.sc.seis.gee.configurator.AbstractHandler;
import edu.sc.seis.gee.configurator.ConfigurationException;

public class OpenMapTask extends JPanel implements SAXTask {

    private ChannelChooserTask channelChooserTask;

    private OpenMap map;

    private MapLegendTask legend;

    private LatLonPoint centerLLP = null;

    private float zoom;

    private List shapeLayerProps = new ArrayList();

    private Projection proj = null;

    private Map params;

    private MapSetup setup;

    private ShowEventsTask showEventsTask;
    private ChannelChooserLoader channelLoader;
    private EventLoader eventLoader;
    
    public void destroy() {}

    public void configure(Map configParams) throws ConfigurationException {
        this.params = configParams;
        setup = new MapSetup(configParams);
        setup.setup();
    }

    private class MapSetup extends AbstractJob {

        public MapSetup(Map params) {
            super("Map Setup");
            this.params = params;
        }

        private Map params;

        private boolean eventsDone = false;

        private boolean chooserDone = false;

        public synchronized void chooserFinished() {
            chooserDone = true;
            if(eventsDone) {
                setFinished();
            } else {
                setStatus("Getting events");
            }
        }

        public synchronized void eventsFinished() {
            eventsDone = true;
            if(chooserDone) {
                setFinished();
            } else {
                setStatus("Getting stations");
            }
        }

        public void runJob() {}

        public void setup() throws ConfigurationException {
            // get event table task from config file
            setStatus("Getting Events");
            String taskId = (String)params.get("eventsourceid");
            if((taskId == null) || (taskId.length() == 0)) {
                throw new ConfigurationException("showEventsTask must be set in configuration file, "
                        + "showEventsTask = '" + taskId + "'");
            }
            TaskAction taskAction = CommonAccess.getCommonAccess()
                    .getTaskAction(taskId);
            showEventsTask = (ShowEventsTask)taskAction.getTask();
            EventTableModel etm = showEventsTask.getEventTableModel();
            setStatus("Getting legend");
            taskId = (String)params.get("legend");
            if((taskId == null) || (taskId.length() == 0)) {
                throw new ConfigurationException("legendTask must be set in configuration file, "
                        + "legendTask = '" + taskId + "'");
            }
            taskAction = CommonAccess.getCommonAccess().getTaskAction(taskId);
            legend = (MapLegendTask)taskAction.getTask();
            // get zoom factor, if existent
            if(params.containsKey("zoom_factor")) {
                setStatus("Getting zoom factor");
                String zoomString = (String)params.get("zoom_factor");
                zoom = Float.parseFloat(zoomString);
            } else
                zoom = Float.NaN;
            // get center point, if existent
            if(params.containsKey("center_lat")
                    && params.containsKey("center_lon") && proj == null) {
                setStatus("Getting center point");
                float lat = Float.parseFloat((String)params.get("center_lat"));
                float lon = Float.parseFloat((String)params.get("center_lon"));
                centerLLP = new LatLonPoint(lat, lon);
            }
            setStatus("Creating map");
            // create map with event table, event selection model, and channel
            // chooser
            Properties[] props = (Properties[])shapeLayerProps.toArray(new Properties[0]);
            if(proj != null) {
                map = new OpenMap(props, proj);
            } else {
                map = new OpenMap(props);
            }
            EventLayer eventLayer = new EventTableLayer(etm,
                                                        showEventsTask.getEventSelectionModel(),
                                                        map,
                                                        new DepthEventColorizer());
            map.setEventLayer(eventLayer);
            if(proj == null && !Float.isNaN(zoom)) {
                map.setZoom(zoom);
            }
            if(proj == null && centerLLP != null) {
                map.getMapBean()
                        .center(new CenterEvent(this,
                                                centerLLP.getLatitude(),
                                                centerLLP.getLongitude()));
            }
            setLayout(new BorderLayout());
            OpenMapTask.this.add(map, BorderLayout.CENTER);
            OpenMapTask.this.add(legend.getLegend(), BorderLayout.NORTH);
            setStatus("Getting stations and events");
            // throw the loading of the channelChooser onto a thread
            channelLoader = new ChannelChooserLoader(params, setup);
            eventLoader = new EventLoader(params, showEventsTask, setup);
        }
    }

    public void invoke() {
        channelLoader.start();
        eventLoader.start();
    }

    protected OpenMap getOpenMap() {
        return map;
    }

    protected LatLonPoint getCenter() {
        return centerLLP;
    }

    protected LatLonPoint getUpperLeftCorner() {
        return map.getMapBean().getProjection().getUpperLeft();
    }

    protected LatLonPoint getLowerRightCorner() {
        return map.getMapBean().getProjection().getLowerRight();
    }

    protected float getScale() {
        return zoom;
    }

    private final class ShapeLayerHandler extends AbstractHandler {

        public ShapeLayerHandler(XMLReader parser, AbstractHandler parent) {
            super(parser, parent);
            alreadyRead = !shapeLayerProps.isEmpty();
        }

        public void endElement(String namespaceURI,
                               String localName,
                               String qName) throws SAXException {
            lastParsedString = lastParsedString.trim();
            if(!alreadyRead) {
                if(localName.equals("shapeLayer")) {
                    shapeLayerProps.add(ShapeLayerPropertiesHandler.getProperties(cur.name,
                                                                                  cur.shapefile,
                                                                                  cur.fillColor,
                                                                                  cur.lineWidth,
                                                                                  cur.threshold,
                                                                                  cur.lineColor));
                } else if(localName.equals("name")) {
                    cur.name = lastParsedString;
                } else if(localName.equals("shapefile")) {
                    cur.shapefile = lastParsedString;
                } else if(localName.equals("fillColor")) {
                    cur.fillColor = lastParsedString;
                } else if(localName.equals("overviewLineWidth")) {
                    cur.lineWidth = Integer.parseInt(lastParsedString);
                } else if(localName.equals("lineWidthThreshold")) {
                    cur.threshold = Integer.parseInt(lastParsedString);
                } else if(localName.equals("lineColor")) {
                    cur.lineColor = lastParsedString;
                }
            }
            if(localName.equals("complexValue")) {
                super.endElement(namespaceURI, localName, qName);
            }
            lastParsedString = "";
        }

        public void characters(char[] ch, int start, int length) {
            lastParsedString += new String(ch, start, length);
        }

        public void startElement(String uri,
                                 String localName,
                                 String qName,
                                 Attributes attributes) {
            if(localName.equals("shapeLayer")) {
                cur = new ShapeLayerPropHolder();
            }
        }

        private boolean alreadyRead = false;

        private String lastParsedString = "";

        private ShapeLayerPropHolder cur;
    }

    private class ShapeLayerPropHolder {

        public String name, shapefile, fillColor, lineColor;

        public int lineWidth = -1, threshold = -1;
    }

    private final class MapViewHandler extends AbstractHandler {

        public MapViewHandler(XMLReader parser, AbstractHandler parent) {
            super(parser, parent);
        }

        public void endElement(String namespaceURI,
                               String localName,
                               String qName) throws SAXException {
            lastParsedString = lastParsedString.trim();
            if(localName.equals("name")) {
                cur.name = lastParsedString;
            } else if(localName.equals("upperLeftLat")) {
                cur.upperLeftLat = Float.parseFloat(lastParsedString);
            } else if(localName.equals("upperLeftLon")) {
                cur.upperLeftLon = Float.parseFloat(lastParsedString);
            } else if(localName.equals("centerLat")) {
                cur.centerLat = Float.parseFloat(lastParsedString);
            } else if(localName.equals("centerLon")) {
                cur.centerLon = Float.parseFloat(lastParsedString);
            } else if(localName.equals("lowerRightLat")) {
                cur.lowerRightLat = Float.parseFloat(lastParsedString);
            } else if(localName.equals("lowerRightLon")) {
                cur.lowerRightLon = Float.parseFloat(lastParsedString);
            } else if(localName.equals("complexValue")) {
                Map viewProps = cur.getValueMap();
                ProjectionHandler projectionHandler;
                if(viewProps != null) {
                    projectionHandler = new ProjectionHandler(cur.getValueMap(),
                                                              cur.name);
                } else if(cur.name != null) {
                    projectionHandler = new ProjectionHandler(cur.name);
                } else {
                    projectionHandler = new ProjectionHandler("World");
                }
                proj = projectionHandler.getProjeciton();
                super.endElement(namespaceURI, localName, qName);
            }
            lastParsedString = "";
        }

        public void characters(char[] ch, int start, int length) {
            lastParsedString += new String(ch, start, length);
        }

        private String lastParsedString = "";

        private MapView cur = new MapView();
    }

    private class MapView {

        public String name = null;

        public float upperLeftLat = Float.NaN, upperLeftLon = Float.NaN,
                centerLat = Float.NaN, centerLon = Float.NaN,
                lowerRightLat = Float.NaN, lowerRightLon = Float.NaN;

        public Map getValueMap() {
            if((name == null) || Float.isNaN(upperLeftLat)
                    || Float.isNaN(upperLeftLon) || Float.isNaN(centerLat)
                    || Float.isNaN(centerLon) || Float.isNaN(lowerRightLat)
                    || Float.isNaN(lowerRightLon)) {
                return null;
            }
            Map propMap = new HashMap();
            propMap.put(name + ".upperLeftLat", upperLeftLat + "");
            propMap.put(name + ".upperLeftLon", upperLeftLon + "");
            propMap.put(name + ".centerLat", centerLat + "");
            propMap.put(name + ".centerLon", centerLon + "");
            propMap.put(name + ".lowerRightLat", lowerRightLat + "");
            propMap.put(name + ".lowerRightLon", lowerRightLon + "");
            return propMap;
        }
    }

    private class ChannelChooserLoader extends Thread {

        Map paramMap;

        MapSetup setup;

        public ChannelChooserLoader(Map params, MapSetup setup) {
            paramMap = params;
            this.setup = setup;
        }

        public void run() {
            try {
                logger.info("ChannelChooserLoader in OpenMapTask run");
                // get channelchooser from config file
                String channelChooserId = (String)paramMap.get("channelChooserTask");
                TaskAction channelChooserAction = CommonAccess.getCommonAccess()
                        .getTaskAction(channelChooserId);
                channelChooserTask = (ChannelChooserTask)channelChooserAction.getTask();
                StationLayer stationLayer = new ChannelChooserLayer(channelChooserTask.getChannelChooser());
                map.setStationLayer(stationLayer);
                logger.info("ChannelChooserLoader in OpenMapTask run done");
            } catch(Throwable e) {
                GlobalExceptionHandler.handle("Problem getting stations for map",
                                              e);
            } finally {
                setup.chooserFinished();
            }
        }
        
    }

    private class EventLoader extends Thread {

        Map paramMap;

        ShowEventsTask showEvents;

        MapSetup setup;

        boolean autoSelect, goBackInTime;

        public EventLoader(Map params, ShowEventsTask set, MapSetup setup) {
            paramMap = params;
            showEvents = set;
            this.setup = setup;
            autoSelect = true;
            if(params.containsKey("autoSelectEvent")) {
                autoSelect = Boolean.getBoolean((String)params.get("autoSelectEvent"));
            }
            goBackInTime = true;
            if(params.containsKey("goBackInTime")) {
                goBackInTime = Boolean.getBoolean((String)params.get("goBackInTime"));
            }
        }

        public void run() {
            try {
                if(goBackInTime) {
                    String taskId = (String)paramMap.get("bigEvents");
                    TaskAction taskAction = CommonAccess.getCommonAccess()
                            .getTaskAction(taskId);
                    QueryEventsTask queryBigEvents = (QueryEventsTask)taskAction.getTask();
                    EventAccessOperations[] events = queryBigEvents.queryEvents();
                    TimeInterval oneWeek = new TimeInterval(7.0, UnitImpl.DAY);
                    for(int i = 0; i < 4 && events.length == 0; i++) {
                        MicroSecondDate curStart = new MicroSecondDate(queryBigEvents.getStartTime());
                        queryBigEvents.setStartTime(curStart.subtract(oneWeek));
                        events = queryBigEvents.queryEvents();
                    }
                }
                if(autoSelect) {
                    float mag = 0.0f;
                    int rowNum = 0;
                    EventAccessOperations[] events = showEvents.getEventTableModel()
                            .getAllEvents();
                    if(events.length > 0) {
                        for(int i = 0; i < events.length; i++) {
                            Origin prefOrigin = EventUtil.extractOrigin(events[i]);
                            float curMag = prefOrigin.getMagnitudes()[0].value;
                            if(curMag > mag) {
                                mag = curMag;
                                rowNum = i;
                            }
                        }
                        if(showEvents.getEventTableModel().getAllEvents().length > 0) {
                            showEvents.getEventSelectionModel()
                                    .setSelectionInterval(rowNum, rowNum);
                        }
                    }
                }
            } catch(Throwable ee) {
                GlobalExceptionHandler.handle(ee);
            }
            setup.eventsFinished();
        }
    }

    public AbstractHandler getConfigureHandler(String name,
                                               XMLReader parser,
                                               AbstractHandler parent) {
        if(name.equals("shapeLayers")) {
            return new ShapeLayerHandler(parser, parent);
        } else if(name.equals("view")) {
            return new MapViewHandler(parser, parent);
        }
        return null;
    }
    
    private static Logger logger = LoggerFactory.getLogger(OpenMapTask.class);
    
}