package atmsdriver.model;

import atmsdriver.model.LoopDetector.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 which are
 * 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 lineID;
    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 lineID, int ldsID, int drop,
            String location, List<LoopDetector> loops, int hwy,
            DIRECTION direction, double postmile)
    {
        this.lineID = lineID;
        this.ldsID = ldsID;
        this.drop = drop;
        this.loops = loops;
        this.location = location;
        this.postmile = postmile;
        this.direction = direction;
        this.routeNumber = hwy;

        this.MLTotVol = getMLTotVol();
        this.OppTotVol = getOPPTotVol();
    }
    
    /**
     * Calculates the total ML Volume.
     * 
     * @return total ML volume.
     */
    private int getMLTotVol()
    {
        int mlTotVol = 0;
        for(LoopDetector loop : loops)
        {
            if(loop.loopLocation.startsWith("ML"))
            {
                mlTotVol += loop.vol;
            }
        }
        return mlTotVol;
    }
    
    /**
     * Calculates the total OPP Volume
     * 
     * @return total OPP volume.
     */
    private int getOPPTotVol()
    {
        int oppTotVol = 0;
        for(LoopDetector loop : loops)
        {
            if(loop.loopLocation.startsWith("OS"))
            {
                oppTotVol += loop.vol;
            }
        }
        return oppTotVol;
    }
    
    /** Returns a string of highways data. If MetaDataOnly is true, you get a full
     *  dump of the highways meta data, which does not include dynamic loop values,
     *  and does include the string location names. If MetaDataOnly is false,
     *  dynamic loop values are included, and unnecessary information like string
     *  location values are included.
     * 
     *  The FEPSimulator takes in the toCondensedFormat() output, with a MetaDataOnly
     *  value of false, over the socket.
     * 
     *  The MetaDataOnly flag should be used to get a full dump of the highways
     *  information. This was used to get the highways_fullmap.txt output.
     * 
     * @param MetaDataOnly Whether you want meta data, or a full dump for FEPSim
     * @return String, highways data in condensed format
     */
    public String toCondensedFormat(boolean MetaDataOnly)
    {
        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(" ");
        if(MetaDataOnly)
        {
            build.append(this.location);
        }
        build.append("\n");
        for (LoopDetector loop : loops)
        {
            build.append(loop.toCondensedFormat(MetaDataOnly));
        }
        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 = "OS";
        if(direction.equals(this.direction))
        {
            laneDir = "ML";
        }
        outputUpdateMessage(dotColor, laneDir);
        
        for(LoopDetector loop : loops)
        {
            if(loop.loopLocation.startsWith(laneDir))
            {
                // UPDATE LOOP WITH VALUES
                loop.updateLoop(dotColor.volume(), dotColor.occupancy());
            }
        }
        
        this.MLTotVol = getMLTotVol();
        this.OppTotVol = getOPPTotVol();
    }
    
    /**
     * Return the color for the lanes in a given direction.
     * @param direction
     * @return character representing color of lanes in given direction
     * '@' = red,  '+' = yellow, '-' = green
     */
    public char getColorByDirection(DIRECTION direction)
    {
        /* For now just use the color of the first lane. 
         * TODO: Average the color in all the lanes for the given direction */

        String laneDir = "OS";
        if(direction.equals(this.direction))
        {
            laneDir = "ML";
        }
        // Examine all the lanes in a given direction
        for(LoopDetector loop : loops)
        {
            if(loop.loopLocation.startsWith(laneDir))
            {
                // Return color according to loop volume
                if (loop.vol == 1) return '@';
                if (loop.vol == 3) return '+';
            }
        }
        // Default case
        return '-';
                
    }
    
    /**
     * Output for updateByDirection. Logs the update to the console.
     * 
     * @param dotcolor
     * @param OPP_ML 
     */
    private void outputUpdateMessage(DOTCOLOR dotcolor, String OPP_ML)
    {
        System.out.printf("Updating %-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());
    }
    
    /**
     * XML tags used for toXML() method.
     */
    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;
        }
    }
    
    /**
     * Returns the Station data in XMLFormat.
     * 
     * @param currElem The current XML <Station> element
     */
    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.lineID)));
        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))];
        }

    }
}
