package edu.sc.seis.sod.subsetter.eventStation;

import java.util.ArrayList;
import java.util.List;

import edu.sc.seis.sod.bag.TauPUtil;
import org.w3c.dom.Element;

import edu.sc.seis.TauP.Arrival;
import edu.sc.seis.TauP.SphericalCoords;
import edu.sc.seis.TauP.TauModelException;
import edu.sc.seis.TauP.TimeDist;
import edu.sc.seis.seisFile.fdsnws.stationxml.Station;
import edu.sc.seis.sod.ConfigurationException;
import edu.sc.seis.sod.SodUtil;
import edu.sc.seis.sod.hibernate.eventpair.MeasurementStorage;
import edu.sc.seis.sod.model.common.Area;
import edu.sc.seis.sod.model.common.BoxAreaImpl;
import edu.sc.seis.sod.model.common.GlobalAreaImpl;
import edu.sc.seis.sod.model.common.QuantityImpl;
import edu.sc.seis.sod.model.common.UnitImpl;
import edu.sc.seis.sod.model.event.CacheEvent;
import edu.sc.seis.sod.model.event.OriginImpl;
import edu.sc.seis.sod.status.StringTree;
import edu.sc.seis.sod.status.StringTreeLeaf;

public class PhaseInteraction implements EventStationSubsetter {

    public PhaseInteraction(Element config) throws ConfigurationException {
        Element element = SodUtil.getElement(config, "modelName");
        if(element != null) modelName = SodUtil.getNestedText(element);
        element = SodUtil.getElement(config, "phaseName");
        if(element != null) phaseName = SodUtil.getNestedText(element);
        element = SodUtil.getElement(config, "interactionStyle");
        if(element != null) interactionStyle = SodUtil.getNestedText(element);
        element = SodUtil.getElement(config, "interactionNumber");
        if(element != null) interactionNumber = Integer.parseInt(SodUtil.getNestedText(element));
        element = SodUtil.getElement(config, "relative");
        if(element != null) phaseInteractionType = (PhaseInteractionType)SodUtil.load(element,
                                                                                      "eventStation");
        element = SodUtil.getElement(config, "absolute");
        if(element != null) phaseInteractionType = (PhaseInteractionType)SodUtil.load(element,
                                                                                      "eventStation");

    }

    public StringTree accept(CacheEvent event,
                             Station station,
                          MeasurementStorage cookieJar) throws Exception {
        if(interactionStyle.equals("PATH")) {
            return new StringTreeLeaf(this, acceptPathInteraction(event,
                                                                         station));
        } else {
            return new StringTreeLeaf(this, acceptPierceInteraction(event, station));
        }
    }

    public boolean acceptPathInteraction(CacheEvent event,
                                         Station station) throws Exception {
        OriginImpl origin = event.getOrigin();
        double originDepth;
        double eventStationDistance;
        originDepth = ((QuantityImpl)origin.getLocation().depth).convertTo(UnitImpl.KILOMETER).getValue();
        List<Arrival> arrivals = TauPUtil.getTauPUtil(modelName).calcTravelTimes(station, origin, new String[] {phaseName});
        eventStationDistance = SphericalCoords.distance(origin.getLocation().latitude,
                                                        origin.getLocation().longitude,
                                                        station.getLatitude().getValue(),
                                                        station.getLongitude().getValue());
        double azimuth = SphericalCoords.azimuth(origin.getLocation().latitude,
                                                 origin.getLocation().longitude,
                                                 station.getLatitude().getValue(),
                                                 station.getLongitude().getValue());
        List<Arrival> requiredArrivals = getRequiredArrival(arrivals);
        if(requiredArrivals.size() == 0) return false;
        if(phaseInteractionType instanceof Relative) {
            return handleRelativePathInteraction(requiredArrivals,
                                                 eventStationDistance);
        } else {
            return handleAbsolutePhaseInteraction(requiredArrivals,
                                                  azimuth,
                                                  origin,
                                                  "PATH");
        }
    }

    public boolean acceptPierceInteraction(CacheEvent event,
                                           Station station) throws Exception {
        double originDepth;
        double eventStationDistance;
        OriginImpl origin = event.getOrigin();
        originDepth = ((QuantityImpl)origin.getLocation().depth).convertTo(UnitImpl.KILOMETER).getValue();
        eventStationDistance = SphericalCoords.distance(origin.getLocation().latitude,
                                                        origin.getLocation().longitude,
                                                        station.getLatitude().getValue(),
                                                        station.getLongitude().getValue());
        double azimuth = SphericalCoords.azimuth(origin.getLocation().latitude,
                                                 origin.getLocation().longitude,
                                                 station.getLatitude().getValue(),
                                                 station.getLongitude().getValue());
        List<Arrival> arrivals = TauPUtil.getTauPUtil(modelName).calcTravelTimes(station, origin, new String[] {phaseName});
        List<Arrival> requiredArrivals = getRequiredArrival(arrivals);
        if(requiredArrivals.size() != 0) {
            if(phaseInteractionType instanceof Relative) {
                return handlePierceRelativePhaseInteraction(requiredArrivals,
                                                            eventStationDistance);
            } else {
                //throw new ConfigurationException("Absolute Area for
                // PhaseInteraction is Not Implemented");
                return handleAbsolutePhaseInteraction(requiredArrivals,
                                                      azimuth,
                                                      origin,
                                                      "PIERCE");
            }
        } else return false;
    }

    public boolean handlePierceRelativePhaseInteraction(List<Arrival> requiredArrivals,
                                                        double eventStationDistance)
            throws Exception {
        for(int counter = 0; counter < requiredArrivals.size(); counter++) {
            TimeDist[] timeDistArray = requiredArrivals.get(counter).getPierce();
            TimeDist timeDist = getRequiredTimeDist(timeDistArray);
            QuantityImpl timeDistDepth = new QuantityImpl(timeDist.getDepth(),
                                                          UnitImpl.KILOMETER);
            ;
            QuantityImpl minDepth;
            QuantityImpl maxDepth;
            QuantityImpl timeDistDistance = new QuantityImpl(timeDist.getDistDeg(), UnitImpl.DEGREE);
            QuantityImpl minDistance;
            QuantityImpl maxDistance;
            if(((Relative)phaseInteractionType).getDepthRange() != null) {
                minDepth = ((Relative)phaseInteractionType).getDepthRange()
                        .getMinDepth();
                maxDepth = ((Relative)phaseInteractionType).getDepthRange()
                        .getMaxDepth();
            } else {
                minDepth = timeDistDepth;
                maxDepth = timeDistDepth;
            }
            if(((Relative)phaseInteractionType).getDistanceRange() != null) {
                if(((Relative)phaseInteractionType).getReference()
                        .equals("STATION")) {
                    timeDistDistance = new QuantityImpl(eventStationDistance - timeDist.getDistDeg(),
                                                        UnitImpl.DEGREE);
                }
                minDistance = ((Relative)phaseInteractionType).getDistanceRange()
                        .getMin();
                maxDistance = ((Relative)phaseInteractionType).getDistanceRange()
                        .getMax();
            } else {
                minDistance = timeDistDistance;
                maxDistance = timeDistDistance;
            }
            if(minDepth.lessThanEqual(timeDistDepth)
                    && maxDepth.greaterThanEqual(timeDistDepth)) {
                if(minDistance.lessThanEqual(timeDistDistance)
                        && maxDistance.greaterThanEqual(timeDistDistance)) { return true; }
            }//end of if checking for depth.
        }//end of for(int counter = 0; ........)
        return false;
    }

    public boolean handleRelativePathInteraction(List<Arrival> requiredArrivals,
                                                 double eventStationDistance)
            throws Exception {
        for(int counter = 0; counter < requiredArrivals.size(); counter++) {
            TimeDist[] timeDistArray = requiredArrivals.get(counter).getPath();
            if(checkForRelativePathInteraction(timeDistArray,
                                               0,
                                               timeDistArray.length,
                                               eventStationDistance,
                                               requiredArrivals.get(counter).getDistDeg())) { return true; }
        }
        return false;
    }

    public boolean checkForRelativePathInteraction(TimeDist[] timeDistArray,
                                                   int start,
                                                   int end,
                                                   double eventStationDistance,
                                                   double totalDistance)
            throws Exception {
        //for(int i = 0; i < timeDistArray.length; i++) {
        int counter = 0;
        if(end < start) return false;
        int mid = (start + end) / 2;
        TimeDist timeDist = timeDistArray[mid];
        QuantityImpl minDistance = null;
        QuantityImpl maxDistance = null;
        QuantityImpl timeDistDistance = new QuantityImpl(timeDist.getDistDeg(), UnitImpl.DEGREE);
        while(counter < totalDistance) {
            QuantityImpl timeDistDepth = new QuantityImpl(timeDist.getDepth(),
                                                          UnitImpl.KILOMETER);
            QuantityImpl minDepth;
            QuantityImpl maxDepth;
            if(((Relative)phaseInteractionType).getDepthRange() != null) {
                minDepth = ((Relative)phaseInteractionType).getDepthRange()
                        .getMinDepth();
                maxDepth = ((Relative)phaseInteractionType).getDepthRange()
                        .getMaxDepth();
            } else {
                minDepth = timeDistDepth;
                maxDepth = timeDistDepth;
            }
            if(((Relative)phaseInteractionType).getDistanceRange() != null) {
                if(((Relative)phaseInteractionType).getReference()
                        .equals("STATION")) {
                    timeDistDistance = new QuantityImpl(eventStationDistance - timeDist.getDistDeg(),
                                                        UnitImpl.DEGREE);
                }
                minDistance = ((Relative)phaseInteractionType).getDistanceRange()
                        .getMin();
                maxDistance = ((Relative)phaseInteractionType).getDistanceRange()
                        .getMax();
            } else {
                minDistance = timeDistDistance;
                maxDistance = timeDistDistance;
            }
            if(minDepth.lessThanEqual(timeDistDepth)
                    && maxDepth.greaterThanEqual(timeDistDepth)) {
                minDistance = new QuantityImpl(minDistance.getValue() + counter,
                                               UnitImpl.DEGREE);
                maxDistance = new QuantityImpl((360 + counter)
                        - minDistance.getValue(), UnitImpl.DEGREE);
                QuantityImpl tempDistance = new QuantityImpl(counter
                        - timeDistDistance.getValue(), UnitImpl.DEGREE);
                if((minDistance.lessThanEqual(timeDistDistance) && maxDistance.greaterThanEqual(timeDistDistance))
                        || (minDistance.lessThanEqual(tempDistance) && maxDistance.greaterThanEqual(tempDistance))) { return true; }
            }
            counter += 360;
        }
        if(end < start) {
            return false;
        } else if(minDistance.greaterThan(timeDistDistance)) {
            return checkForRelativePathInteraction(timeDistArray,
                                                   mid,
                                                   end,
                                                   eventStationDistance,
                                                   totalDistance);
        } else if(maxDistance.lessThan(timeDistDistance)) {
            return checkForRelativePathInteraction(timeDistArray,
                                                   start,
                                                   mid,
                                                   eventStationDistance,
                                                   totalDistance);
        } else return false;
        //}
    }

    public boolean handleAbsolutePhaseInteraction(List<Arrival> requiredArrivals,
                                                  double azimuth,
                                                  OriginImpl origin,
                                                  String type) throws Exception {
        Area area = ((Absolute)phaseInteractionType).getArea();
        for(int i = 0; i < requiredArrivals.size(); i++) {
            TimeDist[] timeDist;
            if(type.equals("PIERCE")) {
                timeDist = requiredArrivals.get(i).getPierce();
            } else {
                timeDist = requiredArrivals.get(i).getPath();
            }
            azimuth = checkForLongway(requiredArrivals.get(i).getDistDeg(), azimuth);
            for(int counter = 0; counter < timeDist.length; counter++) {
                QuantityImpl timeDistDepth = new QuantityImpl(timeDist[0].getDepth(),
                                                              UnitImpl.KILOMETER);
                ;
                QuantityImpl minDepth;
                QuantityImpl maxDepth;
                if(((Absolute)phaseInteractionType).getDepthRange() != null) {
                    minDepth = ((Absolute)phaseInteractionType).getDepthRange()
                            .getMinDepth();
                    maxDepth = ((Absolute)phaseInteractionType).getDepthRange()
                            .getMaxDepth();
                } else {
                    minDepth = timeDistDepth;
                    maxDepth = timeDistDepth;
                }
                if(minDepth.lessThanEqual(timeDistDepth)
                        && maxDepth.greaterThanEqual(timeDistDepth)) {
                    if(area == null || area instanceof GlobalAreaImpl) return true;
                    double tLat = SphericalCoords.latFor(origin.getLocation().latitude,
                                                         origin.getLocation().longitude,
                                                         timeDist[counter].getDepth(),
                                                         azimuth);
                    double tLon = SphericalCoords.lonFor(origin.getLocation().latitude,
                                                         origin.getLocation().longitude,
                                                         timeDist[counter].getDepth(),
                                                         azimuth);
                    if(area instanceof BoxAreaImpl) {
                        BoxAreaImpl boxArea = (BoxAreaImpl)area;
                        if(tLat >= boxArea.min_latitude
                                && tLat <= boxArea.max_latitude
                                && tLon >= boxArea.min_longitude
                                && tLon <= boxArea.max_longitude) { return true; }
                    }//end of if area instanceof BoxArea.
                } //end of if checking for depth.
            }//end of for(int counter = 0.........
        }//end of for(int i= 0; i < requiredArrivals.length; i++)
        return false;
    }

    public TimeDist getRequiredTimeDist(TimeDist[] timeDist) {
        if(timeDist.length == 0) return null;
        double past = timeDist[0].getDepth();
        double current;
        double next;
        for(int counter = 1; counter < timeDist.length - 1; counter++) {
            current = timeDist[counter].getDepth();
            next = timeDist[counter + 1].getDepth();
            if(interactionStyle.equals("TOPSIDE REFLECTION")) {
                //("dist = "+distance+" depth = "+depth+" past = "+past+"
                // current = "+current+" next = "+next);
            }
            if(interactionStyle.equals("TOPSIDE REFLECTION") && past < current
                    && current > next) {
                return timeDist[counter];
            } else if(interactionStyle.equals("BOTTOMSIDE REFLECTION")
                    && past > current && current < next) { return timeDist[counter]; }
            past = current;
        }
        return null;
    }

    public List<Arrival> getRequiredArrival(List<Arrival> arrivals) {
        ArrayList<Arrival> arrayList = new ArrayList<Arrival>();
        for(int counter = 0; counter < arrivals.size(); counter++) {
            String arrivalName = arrivals.get(counter).getName();
            if(phaseName.startsWith("tt")) {
                if(phaseName.equals("tts")
                        && arrivalName.toUpperCase().startsWith("S")) {
                    arrayList.add(arrivals.get(counter));
                } else if(phaseName.equals("ttp")
                        && arrivalName.toUpperCase().startsWith("P")) {
                    arrayList.add(arrivals.get(counter));
                }
            } else if(phaseName.equals(arrivalName)) {
                arrayList.add(arrivals.get(counter));
            }
        }
        return arrayList;
    }

    public double checkForLongway(double distance, double azimuth) {
        while(distance > 360)
            distance = distance - 360;
        if(distance > 180) {
            azimuth = azimuth + 180;
        }
        return azimuth;
    }

    private String modelName = "prem";

    private String phaseName = null;

    private String interactionStyle = null;

    private int interactionNumber = 1;

    private PhaseInteractionType phaseInteractionType = null;

}// PhaseInteraction
