package atmsdriver.model;

import atmsdriver.ConsoleDriver;
import atmsdriver.ConsoleDriver.DOTCOLOR;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 *A Station (LDS or Loop Detector Station) represents a group of lane detectors
 * across all lanes at a particular point on a highway.  A station is
 * identified by its highway number and postmile.  A station has an associated
 * direction used to establish which direction is Main and which is Opposite.
 * The MLTotVol and OppTotVol for a station can be dynamically updated.
 * A station has other attributes: lineNum, ldsID, drop, and location used
 * by the FEP.  A station can be compared to other stations by its postmile.
 *
 * @author John A. Torres
 * @version 9/10/2017
 */
public class Station implements Comparable
{

    /* Static Station meta data */
    final public int lineNum;
    final public int ldsID; // double check
    final public int drop;
    final public String location;
    final public List<LoopDetector> loops;
    final public int routeNumber;
    final public double postmile;
    final public DIRECTION direction;

    /* Dynamic Station data */
    private int MLTotVol;
    private int OppTotVol;

    /* Constructor */
    public Station(int lineNum, int ldsID, int drop,
            String location, List<LoopDetector> loops, int hwy,
            DIRECTION direction, double postmile)
    {
        this.lineNum = lineNum;
        this.ldsID = ldsID;
        this.drop = drop;
        this.loops = loops;
        this.location = location;
        this.postmile = postmile;
        this.direction = direction;
        this.routeNumber = hwy;

        this.MLTotVol = 0;
        this.OppTotVol = 0;
    }

    /**
     * Returns the station metadata in condensed form. This is just a quick
     * script function to make a proper highway metadata configuration file, so
     * that we can read the network faster.
     *
     * @return station metadata
     */
    public String getStationMeta()
    {
        StringBuilder build = new StringBuilder();
        build.append(Integer.toString(this.ldsID));
        build.append(" ");
        build.append(Integer.toString(this.drop));
        build.append(" ");
        build.append(Integer.toString(this.routeNumber));
        build.append(" ");
        build.append(this.direction.getLetter());
        build.append(" ");
        build.append(Double.toString(this.postmile));
        build.append(" ");
        build.append(Integer.toString(loops.size()));
        build.append(" ");
        build.append(this.location);
        build.append("\n");
        for (LoopDetector loop : loops)
        {
            build.append(loop.getLoopMeta());
        }
        return build.toString();
    }

    /**
     * Compare this Station to another by postmile. Note: This might be better
     * as a Comparator since it checks only one field.
     */
    @Override
    public int compareTo(Object otherStation)
    {
        // check for identity
        if (this == otherStation)
        {
            return 0;
        }
        // check that Object is of type Station, if not throw exception
        if (!(otherStation instanceof Station))
        {
            throw new ClassCastException("A Station object expected.");
        }

        // get difference of values
        double otherStationPostmile = ((Station) otherStation).postmile;
        double val = this.postmile - otherStationPostmile;

        // set appropriate comparable return value
        int retval = 0;
        if (val > 0)
        {
            retval = 1;
        }
        else if (val < 0)
        {
            retval = -1;
        }

        return retval;
    }

    /** Determine which lane fields to update based on given direction
     * and update all the loop detectors with the given color.
     * @param direction desired highway direction
     * @param dotColor desired dot color
     */
    public void updateByDirection(DIRECTION direction, DOTCOLOR dotColor) 
    {
        String laneDir = "OPP";
        if(direction.equals(this.direction))
        {
            laneDir = "ML";
        }
        outputUpdateMessage(dotColor, laneDir);
        
        for(LoopDetector loop : loops)
        {
            if(loop.loopLocation.startsWith(laneDir))
            {
                // UPDATE LOOP WITH VALUES
                // TODO: Perhaps enhance DOTCOLOR enum to include constants for
                // vol and occ along with each color.  Then the updateLoop call
                // below could be update(dotColor.volume, dotColor.occ, speed)
                int volume = 0;
                int occ = 0;
                int speed = 0;
                loop.updateLoop(volume, occ, speed);
            }
        }
    }
   
    private void outputUpdateMessage(DOTCOLOR dotcolor, String OPP_ML)
    {
        System.out.printf("Updating route %-3.3s %-5.5s %-3.3s lanes\t %-12.12s "
                + "at postmile %-6.6s to %-7.7s\n", 
                Integer.toString(this.routeNumber), this.direction.name(), 
                OPP_ML, this.location, Double.toString(this.postmile), 
                dotcolor.name());
    }
    
    private static enum XML_TAGS
    {

        STATION("Station"),
        LDS_ID("LDS_ID"),
        LINE_NUM("Line_Num"),
        DROP("Drop"),
        LOOPS("Loops"),
        LOCATION("Location"),
        POST_MILE("Post_Mile"),
        DIRECTION("Direction"),
        FREEWAY("Freeway"),
        ML_TOT_VOL("ML_Tot_Vol"),
        OPP_TOT_VOL("Opp_Tot_Vol");

        String tag;

        private XML_TAGS(String n)
        {
            tag = n;
        }
    }

    public void toXML(Element currElem)
    {
        Document theDoc = currElem.getOwnerDocument();

        Element stationElement = theDoc.createElement(XML_TAGS.STATION.tag);
        currElem.appendChild(stationElement);

        Element ldsIDElement = theDoc.createElement(XML_TAGS.LDS_ID.tag);
        ldsIDElement.appendChild(theDoc.createTextNode(String.valueOf(this.ldsID)));
        stationElement.appendChild(ldsIDElement);

        Element lineNumElement = theDoc.createElement(XML_TAGS.LINE_NUM.tag);
        lineNumElement.appendChild(theDoc.createTextNode(String.valueOf(this.lineNum)));
        stationElement.appendChild(lineNumElement);

        Element dropElement = theDoc.createElement(XML_TAGS.DROP.tag);
        dropElement.appendChild(theDoc.createTextNode(String.valueOf(this.drop)));
        stationElement.appendChild(dropElement);

        Element locationElement = theDoc.createElement(XML_TAGS.LOCATION.tag);
        locationElement.appendChild(theDoc.createTextNode(this.location));
        stationElement.appendChild(locationElement);

        Element postMileElement = theDoc.createElement(XML_TAGS.POST_MILE.tag);
        postMileElement.appendChild(theDoc.createTextNode(String.valueOf(this.postmile)));
        stationElement.appendChild(postMileElement);

        Element directionElement = theDoc.createElement(XML_TAGS.DIRECTION.tag);
        directionElement.appendChild(theDoc.createTextNode("" + this.direction.getLetter()));
        stationElement.appendChild(directionElement);

        Element freewayElement = theDoc.createElement(XML_TAGS.FREEWAY.tag);
        freewayElement.appendChild(theDoc.createTextNode(String.valueOf(this.routeNumber)));
        stationElement.appendChild(freewayElement);

        Element mlElement = theDoc.createElement(XML_TAGS.ML_TOT_VOL.tag);
        mlElement.appendChild(theDoc.createTextNode(String.valueOf(this.MLTotVol)));
        stationElement.appendChild(mlElement);

        Element oppElement = theDoc.createElement(XML_TAGS.OPP_TOT_VOL.tag);
        oppElement.appendChild(theDoc.createTextNode(String.valueOf(this.OppTotVol)));
        stationElement.appendChild(oppElement);

        Element loopsElement = theDoc.createElement(XML_TAGS.LOOPS.tag);
        stationElement.appendChild(loopsElement);

        for (LoopDetector loop : loops)
        {
            loop.toXML(loopsElement);
        }
    }

    /**
     * Enum for freeway direction.
     *
     * @author John A. Torres
     * @version 9/10/2017
     */
    public static enum DIRECTION
    {

        NORTH,
        SOUTH,
        EAST,
        WEST;

        // All the first letters of the values, in order.
        private static String allLetters = "NSEW";

        /**
         * Return the first letter of this enum.
         *
         * @return String first letter of this enum.
         */
        public String getLetter()
        {
            return this.toString().substring(0, 1);
        }

        /**
         * Returns a direction given its first character.
         *
         * @param letter the first character of a direction
         * @return direction corresponding to letter
         * @pre letter must be one of allLetters
         */
        public static DIRECTION toDirection(String letter)
        {
            return values()[allLetters.indexOf(letter.charAt(0))];
        }

    }
}
