package org.opentrafficsim.demo.ntm;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Map;
import java.util.NavigableMap;
import org.opentrafficsim.core.unit.FrequencyUnit;
import org.opentrafficsim.core.unit.TimeUnit;
import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar.Abs;
import org.opentrafficsim.demo.ntm.Node.TrafficBehaviourType;
import org.opentrafficsim.demo.ntm.trafficdemand.FractionOfTripDemandByTimeSegment;
import org.opentrafficsim.demo.ntm.trafficdemand.TripInfoTimeDynamic;

/**
 * <p>
 * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
 * reserved. <br>
 * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
 * <p>
 * @version 29 Oct 2014 <br>
 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
 * @author <a href="http://Hansvanlint.weblog.tudelft.nl">Hans van Lint</a>
 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
 * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
 * @author <a href="http://www.citg.tudelft.nl">Yufei Yuan</a>
 */
public class NTMsimulation
{
    /**
     * @param model that is being simulated
     */
    static int steps = 0;

    static int MAXSTEPS = 150;

    /** */
    static BufferedWriter debugOut = null;

    /** */
    static BufferedWriter dataLaneLengthOut = null;

    /** */
    static BufferedWriter dataLanesPerCellOut = null;

    /** */
    static BufferedWriter dataAccumulationOut = null;

    /** */
    static BufferedWriter dataParametersFDOut = null;

    /** */
    static BufferedWriter dataFDOut = null;

    /** */
    static Double[][] accumulation = new Double[999][999];

    /** */
    static Double[][] parametersFD = new Double[999][3];

    /** */
    static Double[] laneLength = new Double[999];

    /** */
    static Double[] lanesPerCell = new Double[999];

    /** */
    static String[] cellID = new String[999];

    /** */
    static int numberOfCells = 0;

    /**
     * @param model
     * @throws IOException
     */
    public static void simulate(final NTMModel model) throws IOException
    {
        @SuppressWarnings("unchecked")
        DoubleScalar.Abs<TimeUnit> currentTime = null;
        // debug for fastest path and write data??
        steps++;

        if (steps == MAXSTEPS)
        {
            model.DEBUG = false;
        }
        try
        {
            currentTime =
                    new DoubleScalar.Abs<TimeUnit>(model.getSettingsNTM().getStartTimeSinceMidnight().getSI()
                            + model.getSimulator().getSimulatorTime().get().getSI(), TimeUnit.SECOND);
        }
        catch (RemoteException exception2)
        {
            exception2.printStackTrace();
        }

        // retrieve information from the Area Graph containing the NTM areas and the selected highways

        // The AreaGraph contains EDGES with differing characteristics:
        // - "NTM"-links between NTM areas
        // - "Cordon" links at the border of the study area that act as feeders and sinks of traffic to the "real" Areas
        // - "Flow" links representing higher order roads with behaviour that deviates from the NTM area links
        // (only for specific "main" roads)

        // The VERTICES (type: BoundedNode) represent the NTM areas, or the entrance / exit of a Flow link.
        // NTM nodes and FlowLink entrances/exits are connected by a "Transfer" link
        //
        // The "NTM" links between a pair of nodes, visualise the connection of that node to its "neighbours" with NTM
        // characteristics
        // The "Flow" links represent a connection where traffic behaves differently than in the NTM areas:
        // once traffic is on a homogeneous link, the capacity remains stable
        //
        // The simulation of Traffic becomes a nested process:
        // - the NTM process is the parent of the simulation
        // - the Flow process acts as a "child" process
        // ********************************************************************************************************
        // STEP 1 of the simulation describes the initialisation of Demand from the Traffic Demand file:
        // - this is generated by the OD matrix from the model

        // first loop through the network nodes and select the NTM and Cordon "Area nodes"
        // These nodes generate traffic from the trip demand file (feeders)

        // There are two ESSENTIAL variables / class types:
        // - the Node.ClassBehaviour represents the aggregated traffic flow dynamics between nodes/cells
        // - the TripInfoTimeDynamic of an Area, showing for every OD-pair:
        // . - number of trips between origin (or intermediate areas on a path from O to D) - destination pairs (from
        // . . demand file)
        // . - neighbour area on path form Origin to Destination
        // . - time profile curve of trip departures from this origin (only used for the real origins!)
        // . - accumulated cars in this area/node/cell, heading on the way to a certain destination
        // . - flow in this time-step to neighbour from this area to destination

        // for testing we open a file and write some results:
        // TODO testing
        // Initiate trips from OD to first Area (Origin)

        Map<String, Map<String, TripInfoTimeDynamic>> trips;

        if (model.COMPRESS_AREAS)
        {
            trips = model.getCompressedTripDemand().getTripInfo();
        }
        else
        {
            trips = model.getTripDemand().getTripInfo();
        }
        if (model.DEBUG && steps == 1)
        {
            File file = new File("D:/gtamminga/workspace/ots-ntm/src/main/resources/gis/debug1/NTMoutputTest.txt");

            // if file doesnt exists, then create it
            if (!file.exists())
            {
                try
                {
                    file.createNewFile();
                }
                catch (IOException exception)
                {
                    exception.printStackTrace();
                }
            }

            try
            {
                debugOut = new BufferedWriter(new FileWriter(file));
            }
            catch (IOException exception1)
            {
                exception1.printStackTrace();
            }
        }

        // FIRST: Loop through all nodes and reset the DemandToEnter (for nodes with more than one entrance) to zero
        for (BoundedNode origin : model.getAreaGraph().vertexSet())
        {
            {
                // only the feeding areas of the type NTM and Cordon can generate new traffic from the trip demand
                // matrix
                if (origin.getBehaviourType() == TrafficBehaviourType.NTM
                        || origin.getBehaviourType() == TrafficBehaviourType.CORDON)
                {
                    origin.getCellBehaviour().setDemandToEnter(0);
                }
                else if (origin.getBehaviourType() == TrafficBehaviourType.FLOW)
                {
                    for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour().getTripInfoByNodeMap()
                            .values())
                    {
                        if (tripInfoByDestination.getNeighbour().getBehaviourType() == TrafficBehaviourType.FLOW)
                        {
                            LinkCellTransmission ctmLink =
                                    (LinkCellTransmission) model.getAreaGraph()
                                            .getEdge(origin, (BoundedNode) tripInfoByDestination.getNeighbour())
                                            .getLink();
                            // add the demand of trips that want to enter the first cell

                            // **** RELEVANT
                            ctmLink.getCells().get(0).getCellBehaviourFlow().setDemandToEnter(0);
                        }
                    }
                }
            }
        }

        // Loop through all areas to detect the trips to the destination area
        for (BoundedNode origin : model.getAreaGraph().vertexSet())
        {
            try
            {
                // only the feeding areas of the type NTM and Cordon can generate new traffic from the trip demand
                // matrix
                if (origin.getBehaviourType() == TrafficBehaviourType.NTM
                        || origin.getBehaviourType() == TrafficBehaviourType.CORDON)
                {
                    // the variable CellBehaviour(NTM) defines the traffic process within an area (the area is
                    // represented by the "BoundedNode"). This can be NTM behaviour, Cell transmission or other.

                    // during the simulation traffic enters and leaves the NTM areas. The number of "accumulated cars"
                    // represents the net balance of cars within the Nodes/areas. The new demand will be added!
                    // The variable TripsFrom contains information on trips from an origin/node to ALL other
                    // destinations.
                    // This origin can be the real origin or an intermediate area on the path to destination
                    // (neighbours).
                    // The structure (or Class in Java) named TripInfoDynamic is stored in a HashMap (lookup array) that
                    // contains this information for all destinations separately.
                    Map<String, TripInfoTimeDynamic> tripsFrom = trips.get(origin.getId());
                    // loop through all destinations to get total demand and supply per origin and add the new trips
                    for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour().getTripInfoByNodeMap()
                            .values())
                    // for (BoundedNode destinationNode : model.getAreaGraph().vertexSet())
                    // {
                    // only select the final destinations: where Trips are heading to
                    // if (tripsFrom.containsKey(destinationNode.getId()))
                    {
                        // retrieve the TRIPS from the demand file (2 hour period)
                        // first the total within the defined period
                        BoundedNode destinationNode = (BoundedNode) tripInfoByDestination.getDestination();
                        if (tripsFrom.get(destinationNode.getId()) != null)
                        {
                            double startingTrips = tripsFrom.get(destinationNode.getId()).getNumberOfTrips();
                            // get the share of Trips of this time slice (NTM simulation step of 10 seconds)
                            if (startingTrips > 0.0)
                            {
                                // retrieve the departure time curve showing the fractions of demand by time-slice
                                NavigableMap<Abs<TimeUnit>, FractionOfTripDemandByTimeSegment> curve =
                                        tripsFrom.get(destinationNode.getId()).getDepartureTimeProfile()
                                                .getDepartureTimeCurve();
                                Object ceilingKey = curve.ceilingKey(currentTime);
                                // The variable segment of the type (FractionOfTripDemandByTimeSegment) contains the
                                // duration (in time units) of this segment and the fraction
                                if (ceilingKey == null)
                                {
                                    System.out.println("NTMSimulation line 349: Type road should not be possible"
                                            + ceilingKey);
                                }
                                FractionOfTripDemandByTimeSegment segment = curve.get(ceilingKey);
                                // the share is adjusted by the TimeStepDuration of the simulation
                                double share =
                                        segment.getShareOfDemand()
                                                * model.getSettingsNTM().getTimeStepDurationNTM().getSI()
                                                / segment.getDuration().getSI();
                                startingTrips = share * startingTrips;
                                // these new Trips are added to the TRIPS that are already on their way (passing an NTM
                                // area): the AccumulatedCars specified by their specific destination (nodeTo.getId())
                                // TripInfoByDestination tripInfoByDestination =
                                // origin.getCellBehaviour().getTripInfoByNodeMap().get(destinationNode);
                                if (tripInfoByDestination != null)
                                {
                                    // **** RELEVANT
                                    tripInfoByDestination.addAccumulatedCarsToDestination(startingTrips);
                                }

                                // increases the total number of accumulated cars in the area, that is
                                // used for NTM computations
                                // **** RELEVANT
                                origin.getCellBehaviour().addAccumulatedCars(startingTrips);

                            }
                        }
                        // }
                    }

                    // only production, if there are accumulated cars!!
                    if (origin.getCellBehaviour().getAccumulatedCars() > 0.0)
                    {
                        // The variable CellBehaviour stores the number of accumulated cars in this cell, and
                        // additionally
                        // the demandToEnter (cars that want to enter an area/cell)
                        // First update the accumulation of cars within the Area (we added the new Trips)
                        // ACCUMULATION
                        // the demand and supply of traffic is based on the type of Cell that is considered
                        // DEMAND_LEAVE && SUPPLY
                        if (origin.getBehaviourType() == TrafficBehaviourType.NTM)
                        {
                            // CellBehaviourNTM extends CellBehaviour (additional or different behaviour)
                            CellBehaviourNTM cellBehaviourNTM = (CellBehaviourNTM) origin.getCellBehaviour();
                            // compute the total Demand (production) from an Area to all other Destinations (the level
                            // is
                            // based on the accumulation, the capacity of an area and the NFD algorithm).
                            // The new demand of this area is derived via the method RetrieveDemand that is based on the
                            // network fundamental diagram (see there for further details)
                            // TODO implement roadLength and length simulation step !!!!!!!!!!!!!!!!!!!!!!!!

                            // **** RELEVANT
                            Abs<FrequencyUnit> tripByHour =
                                    cellBehaviourNTM.retrieveDemand(origin.getCellBehaviour().getAccumulatedCars(),
                                            cellBehaviourNTM.getMaxCapacity(), cellBehaviourNTM.getParametersNTM());
                            double tripByTimeStep =
                                    model.getSettingsNTM().getTimeStepDurationNTM().getSI()
                                            * tripByHour.getUnit().getConversionFactorToStandardUnit()
                                            * tripByHour.doubleValue();
                            cellBehaviourNTM.setDemand(tripByTimeStep);
                            // compute the total supply (maximum) from neighbours to this Area (again based on the
                            // accumulation and NFD/area characteristics)
                            // TODO implement roadLength and length simulation step !!!!!!!!!!!!!!!!!!!!!!!!

                            // **** RELEVANT
                            tripByHour =
                                    cellBehaviourNTM.retrieveSupply(origin.getCellBehaviour().getAccumulatedCars(),
                                            cellBehaviourNTM.getMaxCapacity(), cellBehaviourNTM.getParametersNTM());
                            tripByTimeStep =
                                    model.getSettingsNTM().getTimeStepDurationNTM().getSI()
                                            * tripByHour.getUnit().getConversionFactorToStandardUnit()
                                            * tripByHour.doubleValue();
                            cellBehaviourNTM.setSupply(tripByTimeStep);
                        }

                        // the border, or CORDON areas, act as sink/source for traffic
                        // The flow nodes act as entrances, intermediate or exits of the cell transmission model
                        // For this last category, we assume that the links are putting a restriction on capacity
                        else if (origin.getBehaviourType() == TrafficBehaviourType.CORDON)
                        {
                            // demand is the sum of new demand and accumulated traffic from previous time steps
                            // (possibly if the neighbour area does not accept all traffic)
                            // Within cordon areas all traffic can directly move to another area or Node!

                            // **** RELEVANT
                            origin.getCellBehaviour().setDemand(origin.getCellBehaviour().getAccumulatedCars());
                            // the total supply is infinite for Cordon and Flow nodes (sinks with no limit on in-flow)
                            // TODO can go to the initiation of this cellBehaviour

                            // **** RELEVANT
                            origin.getCellBehaviour().setSupply(java.lang.Double.POSITIVE_INFINITY);
                        }
                    }

                    // NEXT STEP!!!!
                    // after determining new demand, we compute the demand for traffic to other nodes or areas by
                    // looping through all nodes on a path from every origin
                    for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour().getTripInfoByNodeMap()
                            .values())
                    {
                        // The tripInfoByNode includes information about the trips, but specified by all destination
                        // zones
                        // In this step we are interested in the first zone we encounter ("neighbour") of the
                        // cars on their path to a certain destination Area.
                        // Therefore we retrieve this neighbour.
                        BoundedNode neighbour = (BoundedNode) tripInfoByDestination.getNeighbour();
                        // Compute the share of the accumulated trips to a certain destination as part of the
                        // total accumulation
                        if (neighbour != null)
                        {
                            if (tripInfoByDestination.getAccumulatedCarsToDestination() > 0)
                            {
                                // this potential out-flow is heading to the neighbour that is on its path to
                                // destination

                                // **** RELEVANT
                                double demandToNeighbour =
                                        (tripInfoByDestination.getAccumulatedCarsToDestination() / origin
                                                .getCellBehaviour().getAccumulatedCars())
                                                * origin.getCellBehaviour().getDemand();

                                // **** RELEVANT
                                tripInfoByDestination.setDemandToDestination(demandToNeighbour);
                                // first add these trips to the number of trips that want to transfer to their
                                // neighbour
                                if (neighbour.getBehaviourType() == TrafficBehaviourType.NTM
                                        || neighbour.getBehaviourType() == TrafficBehaviourType.CORDON)
                                {
                                    // this flow is also added to the total sum of traffic that wants to enter this
                                    // neighbour Area.

                                    // **** RELEVANT
                                    neighbour.getCellBehaviour().addDemandToEnter(demandToNeighbour);
                                }
                                // the trips that enter the flow Links, are processed immediately (no timestep delay) to
                                // the first cell of the transmission link
                                else if (neighbour.getBehaviourType() == TrafficBehaviourType.FLOW)
                                {
                                    // determine the next movement: is it a flow link or a link to a cordon or NTM area?
                                    TripInfoByDestination neighbourTripInfoByDestination =
                                            neighbour.getCellBehaviour().getTripInfoByNodeMap()
                                                    .get(tripInfoByDestination.getDestination());
                                    BoundedNode nextNeighbour =
                                            (BoundedNode) neighbourTripInfoByDestination.getNeighbour();
                                    // when entering a flow link, loop through all succeeding flow links until reaching
                                    // a NTM or CORODN node :
                                    if (nextNeighbour.getBehaviourType() == TrafficBehaviourType.FLOW)
                                    {
                                        // Retrieve the cell transmission link
                                        // all cells (should) have identical characteristics
                                        LinkCellTransmission ctmLink =
                                                (LinkCellTransmission) model.getAreaGraph()
                                                        .getEdge(neighbour, nextNeighbour).getLink();
                                        // add the demand of trips that want to enter the first cell

                                        // **** RELEVANT
                                        ctmLink.getCells().get(0).getCellBehaviourFlow()
                                                .addDemandToEnter(demandToNeighbour);
                                    }
                                }
                                else if (neighbour.getBehaviourType() == TrafficBehaviourType.ROAD)
                                {
                                    System.out.println("NTMSimulation line 349: Type road should not be possible");
                                }

                            }
                        }
                        else
                        {
                            System.out.println("NTMSimulation line 379: Strange: no neighbour");
                        }
                        // In the next step, see whether this demand from nodes is able to enter completely or
                        // just partly (when supply is restricted)

                    }

                }

                else if (origin.getBehaviourType() == TrafficBehaviourType.FLOW)
                {
                    // Update demand and supply of the flow links
                    // every node has "packages" of information for trips to a certain destination: the
                    // TripInfoByDestination
                    // loop through this info to retrieve the demand by destination
                    for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour().getTripInfoByNodeMap()
                            .values())
                    {
                        if (tripInfoByDestination.getNeighbour().getId() != null
                                && tripInfoByDestination.getNeighbour() != null)
                        {
                            // retrieve the neighbour area on the path to a certain destination
                            BoundedNode neighbour = (BoundedNode) tripInfoByDestination.getNeighbour();
                            if (neighbour != null)
                            {
                                // only the flow links are considered in the CTM model, so we need a Flow node at the
                                // end of the link
                                if (neighbour.getBehaviourType() == TrafficBehaviourType.FLOW)
                                {
                                    // Do CTM
                                    // In case of Cell Transmission Links, there is an intermediate process of traffic
                                    // moving over a link. The demand to this link is computed.
                                    // In Step 3 see if this demand is below the capacity of the link (supply)
                                    try
                                    {
                                        // Retrieve the cell transmission link
                                        // all cells (should) have identical characteristics
                                        LinkCellTransmission ctmLink =
                                                (LinkCellTransmission) model.getAreaGraph().getEdge(origin, neighbour)
                                                        .getLink();
                                        // Loop through the cells and do transmission
                                        for (FlowCell cell : ctmLink.getCells())
                                        {
                                            double accumulationCell = cell.getCellBehaviourFlow().getAccumulatedCars();
                                            // retrieve the current info of trips in this cell
                                            // at the first cell, demand comes from the FlowNode!!
                                            // maximum to enter this cell
                                            // **** RELEVANT
                                            DoubleScalar.Abs<FrequencyUnit> tripByHour =
                                                    cell.getCellBehaviourFlow().retrieveSupply(
                                                            accumulationCell,
                                                            cell.getCellBehaviourFlow()
                                                                    .getParametersFundamentalDiagram(),
                                                            cell.getNumberOfLanes());
                                            double tripByStep =
                                                    model.getSettingsNTM().getTimeStepDurationNTM().getSI()
                                                            * tripByHour.getUnit().getConversionFactorToStandardUnit()
                                                            * tripByHour.doubleValue();
                                            cell.getCellBehaviourFlow().setSupply(tripByStep);
                                            tripByHour =
                                                    cell.getCellBehaviourFlow().retrieveDemand(
                                                            accumulationCell,
                                                            cell.getCellBehaviourFlow()
                                                                    .getParametersFundamentalDiagram(),
                                                            cell.getNumberOfLanes());
                                            tripByStep =
                                                    model.getSettingsNTM().getTimeStepDurationNTM().getSI()
                                                            * tripByHour.getUnit().getConversionFactorToStandardUnit()
                                                            * tripByHour.doubleValue();
                                            cell.getCellBehaviourFlow().setDemand(tripByStep);

                                            TripInfoByDestination cellInfoByNodeMap =
                                                    cell.getCellBehaviourFlow().getTripInfoByNodeMap()
                                                            .get(tripInfoByDestination.getDestination());
                                            double demandToDestination =
                                                    cellInfoByNodeMap.getAccumulatedCarsToDestination()
                                                            / accumulationCell
                                                            * cell.getCellBehaviourFlow().getDemand();

                                            // **** RELEVANT
                                            cellInfoByNodeMap.setDemandToDestination(demandToDestination);

                                        }

                                    }
                                    catch (Exception e)
                                    {
                                        e.printStackTrace();
                                    }
                                }
                            }
                            else
                            {
                                System.out.println("CTMsimulation line 387: no neighbour...");
                            }
                        }
                        else
                        {
                            System.out.println("NTMSimulation line 44: Strange and to repair: nodeTo equals null");
                        }

                    }

                }

            }

            // in the next steps, the dynamics of demand and supply create a certain flow between areas
            catch (Exception e)
            {
                e.printStackTrace();
            }

        }

        // ********************************************************************************************************
        // STEP 2:
        // Simulate the CellTranmission model
        // This phase identifies how trips proceed through the link and results in the traffic state of the flow cells
        // within the CTM link and the amount of traffic that wants to enter the end Node of the CTM link
        // If this Node provides the entrance to a new Cell Transmission Link this is implemented in the simulation
        // if that Node provides a transfer to an NTM of Cordon area it captures the trips that want to enter that node

        // Simulate this in between the next NTM simulation step, possibly several times!! Adjust to simulation time
        // step length
        // for (int i = 0; i <= 5; i++)
        // {
        // CTMsimulation.simulateCellTransmission(model);
        // }
        // ********************************************************************************************************
        // STEP 3:
        // Monitor whether the demand of traffic from outside areas is able to enter a certain Area
        // Perhaps SUPPLY poses an upper bound on the Demand!
        for (BoundedNode origin : model.getAreaGraph().vertexSet())
        {
            try
            {
                if (origin.getCellBehaviour().getAccumulatedCars() > 0.0)
                {
                    // Trips always start from an NTM or Cordon Node. If they pass a Flow Node they by definition enter
                    // a flow Link. The simulation of these flow links are carried out sequentially
                    if (origin.getBehaviourType() == TrafficBehaviourType.NTM
                            || origin.getBehaviourType() == TrafficBehaviourType.CORDON)
                    {
                        for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour()
                                .getTripInfoByNodeMap().values())
                        {
                            if (tripInfoByDestination.getAccumulatedCarsToDestination() > 0)
                            {
                                BoundedNode neighbour = (BoundedNode) tripInfoByDestination.getNeighbour();
                                BoundedNode destination = (BoundedNode) tripInfoByDestination.getDestination();

                                if (neighbour.getBehaviourType() == TrafficBehaviourType.NTM
                                        || neighbour.getBehaviourType() == TrafficBehaviourType.CORDON)
                                {
                                    // retrieve the neighbour area on the path to a certain destination
                                    if (neighbour != null && tripInfoByDestination.getDemandToDestination() > 0.0)
                                    {
                                        // retrieve the type of CellBehaviour (showing the demand and supply
                                        // characteristics of this neighbour)
                                        double demandToNeighbour = tripInfoByDestination.getDemandToDestination();
                                        // compute the share of traffic that wants to enter this Neighbour area from a
                                        // certain origin - destination pair as part of the total demand that wants to
                                        // enter the neighbour cell. The total supply to the neighbour may be restricted
                                        // (by calling getSupply that provides the maximum Supply).
                                        double totalDemand =
                                                Math.min(neighbour.getCellBehaviour().getDemandToEnter(), neighbour
                                                        .getCellBehaviour().getSupply());
                                        double flowToNeighbour =
                                                (demandToNeighbour / neighbour.getCellBehaviour().getDemandToEnter())
                                                        * totalDemand;
                                        // Compute the final flow based on the share of Trips
                                        // to a certain destination and this maximum supply of the Cell.
                                        // set the final flow to the neighbour

                                        // **** RELEVANT
                                        neighbour.getCellBehaviour().addAccumulatedCars(flowToNeighbour);
                                        neighbour.getCellBehaviour().addAccumulatedCars(flowToNeighbour);
                                        tripInfoByDestination.addAccumulatedCarsToDestination(-flowToNeighbour);
                                        origin.getCellBehaviour().addAccumulatedCars(-flowToNeighbour);
                                    }
                                    else if (tripInfoByDestination.getDemandToDestination() == 0.0)
                                    {
                                        System.out.println("NTMSimulation line 471: no demand to neighbour");
                                    }
                                    else
                                    {
                                        System.out.println("NTMSimulation line 475: no neighbour");
                                    }

                                }

                                // else do the simulation of flow links
                                else if (neighbour.getBehaviourType() == TrafficBehaviourType.FLOW)
                                {
                                    // determine the next movement: is it a flow link or a link to a cordon or NTM area?
                                    TripInfoByDestination neighbourTripInfoByDestination =
                                            neighbour.getCellBehaviour().getTripInfoByNodeMap().get(destination);
                                    BoundedNode nextNeighbour =
                                            (BoundedNode) neighbourTripInfoByDestination.getNeighbour();
                                    double demandToDestination = tripInfoByDestination.getDemandToDestination();
                                    LinkCellTransmission ctmLink =
                                            (LinkCellTransmission) model.getAreaGraph()
                                                    .getEdge(neighbour, nextNeighbour).getLink();
                                    // when entering a flow link, loop through all succeeding flow links until reaching
                                    // a NTM or CORODN node :#
                                    FlowCell lastCell = null;
                                    FlowCell prevCell = null;
                                    while (nextNeighbour.getBehaviourType() == TrafficBehaviourType.FLOW)
                                    {
                                        // Retrieve the cell transmission link
                                        // all cells (should) have identical characteristics

                                        // Loop through the cells and do transmission
                                        for (FlowCell cell : ctmLink.getCells())
                                        {
                                            double demandCell;
                                            double supplyCell;
                                            // at the first cell, demand comes from the FlowNode!!
                                            if (ctmLink.getCells().indexOf(cell) == 0)
                                            {
                                                prevCell = cell;
                                                if (lastCell != null)
                                                {
                                                    demandToDestination =
                                                            lastCell.getCellBehaviourFlow().getTripInfoByNodeMap()
                                                                    .get(destination).getDemandToDestination();
                                                    demandCell = prevCell.getCellBehaviourFlow().getDemand();

                                                }
                                                if (demandToDestination > 0)
                                                {
                                                    // maximum to enter this cell
                                                    supplyCell = cell.getCellBehaviourFlow().getSupply();
                                                    if (lastCell != null)
                                                    {
                                                        demandCell = prevCell.getCellBehaviourFlow().getDemand();
                                                    }
                                                    else
                                                    {
                                                        demandCell = cell.getCellBehaviourFlow().getDemandToEnter();
                                                    }
                                                    // tripInfoByDestination.getDemandToNeighbour();
                                                    double flowToDestination =
                                                            Math.min(supplyCell / demandCell, 1.0)
                                                                    * demandToDestination;
                                                    // **** RELEVANT
                                                    cell.getCellBehaviourFlow().addAccumulatedCars(flowToDestination);
                                                    cell.getCellBehaviourFlow().getTripInfoByNodeMap().get(destination)
                                                            .addAccumulatedCarsToDestination(flowToDestination);
                                                    if (lastCell != null)
                                                    {
                                                        lastCell.getCellBehaviourFlow().getTripInfoByNodeMap()
                                                                .get(destination)
                                                                .addAccumulatedCarsToDestination(-flowToDestination);
                                                        lastCell.getCellBehaviourFlow().addAccumulatedCars(
                                                                -flowToDestination);
                                                    }
                                                    else
                                                    {
                                                        tripInfoByDestination
                                                                .addAccumulatedCarsToDestination(-flowToDestination);
                                                        origin.getCellBehaviour()
                                                                .addAccumulatedCars(-flowToDestination);
                                                    }

                                                }
                                            }
                                            else
                                            {
                                                // determine demand by cell
                                                // maximum to enter this cell
                                                demandToDestination =
                                                        prevCell.getCellBehaviourFlow().getTripInfoByNodeMap()
                                                                .get(destination).getDemandToDestination();
                                                // determine the downstream cells (but the last):
                                                if (ctmLink.getCells().indexOf(cell) < ctmLink.getCells().size())
                                                {
                                                    if (demandToDestination > 0)
                                                    {
                                                        // FlowCell downStreamCell =
                                                        // ctmLink.getCells().get(
                                                        // ctmLink.getCells().indexOf(cell) + 1);
                                                        supplyCell = cell.getCellBehaviourFlow().getSupply();
                                                        demandCell = prevCell.getCellBehaviourFlow().getDemand();
                                                        // tripInfoByDestination.getDemandToNeighbour();
                                                        double flowToDestination =
                                                                Math.min(supplyCell / demandCell, 1.0)
                                                                        * demandToDestination;
                                                        // **** RELEVANT
                                                        cell.getCellBehaviourFlow().addAccumulatedCars(
                                                                flowToDestination);
                                                        cell.getCellBehaviourFlow().getTripInfoByNodeMap()
                                                                .get(destination)
                                                                .addAccumulatedCarsToDestination(flowToDestination);
                                                        prevCell.getCellBehaviourFlow().getTripInfoByNodeMap()
                                                                .get(destination)
                                                                .addAccumulatedCarsToDestination(-flowToDestination);
                                                        prevCell.getCellBehaviourFlow().addAccumulatedCars(
                                                                -flowToDestination);

                                                    }
                                                }
                                                // the last cell of the cell transmission link
                                                if (ctmLink.getCells().indexOf(cell) == ctmLink.getCells().size() - 1)
                                                // at the last cell, the trips are forwarded to the next Node: could be
                                                // NTM, Flow or Cordon
                                                // if it is a NTM or Flow, there could be a back loop
                                                {
                                                    TripInfoByDestination nextNeighbourTripInfoByDestination =
                                                            nextNeighbour.getCellBehaviour().getTripInfoByNodeMap()
                                                                    .get(tripInfoByDestination.getDestination());
                                                    BoundedNode nextNextNeighbour =
                                                            (BoundedNode) nextNeighbourTripInfoByDestination
                                                                    .getNeighbour();

                                                    if (nextNextNeighbour.getBehaviourType() == TrafficBehaviourType.NTM
                                                            || nextNextNeighbour.getBehaviourType() == TrafficBehaviourType.CORDON)
                                                    {
                                                        if (demandToDestination > 0)
                                                        {
                                                            supplyCell =
                                                                    nextNextNeighbour.getCellBehaviour().getSupply();
                                                            demandCell =
                                                                    nextNextNeighbour.getCellBehaviour()
                                                                            .getDemandToEnter();
                                                            // tripInfoByDestination.getDemandToNeighbour();
                                                            double flowToDestination =
                                                                    Math.min(supplyCell / demandCell, 1.0)
                                                                            * demandToDestination;
                                                            // **** RELEVANT
                                                            nextNextNeighbour.getCellBehaviour().addAccumulatedCars(
                                                                    flowToDestination);
                                                            if (nextNextNeighbour.getCellBehaviour()
                                                                    .getTripInfoByNodeMap().get(destination) == null)
                                                            {
                                                                // TODO collect finished trips
                                                                System.out.println("Finished a trip!");
                                                            }
                                                            else
                                                            {
                                                                nextNextNeighbour
                                                                        .getCellBehaviour()
                                                                        .getTripInfoByNodeMap()
                                                                        .get(destination)
                                                                        .addAccumulatedCarsToDestination(
                                                                                flowToDestination);
                                                            }
                                                            cell.getCellBehaviourFlow()
                                                                    .getTripInfoByNodeMap()
                                                                    .get(destination)
                                                                    .addAccumulatedCarsToDestination(-flowToDestination);
                                                            cell.getCellBehaviourFlow().addAccumulatedCars(
                                                                    -flowToDestination);
                                                        }
                                                        neighbour = nextNeighbour;
                                                        nextNeighbour = nextNextNeighbour;
                                                    }
                                                    else if (nextNextNeighbour.getBehaviourType() == TrafficBehaviourType.FLOW)
                                                    {
                                                        // set the variables for the next loop (while...)
                                                        {
                                                            neighbour = nextNeighbour;
                                                            nextNeighbour = nextNextNeighbour;
                                                            ctmLink =
                                                                    (LinkCellTransmission) model.getAreaGraph()
                                                                            .getEdge(neighbour, nextNeighbour)
                                                                            .getLink();
                                                            lastCell = cell;
                                                        }
                                                    }
                                                }
                                                prevCell = cell;
                                            }

                                        }
                                    }
                                }

                            }

                        }
                    }
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }

        // Writing output data
        if (model.DEBUG && steps == 1)
        {
            File fileLaneLength =
                    new File(
                            "D:/gtamminga/workspace/ots-ntm/src/main/resources/gis/debug1/output/NTMoutputLaneLength.txt");
            dataLaneLengthOut = createWriter(fileLaneLength);
            File fileAccumulation =
                    new File(
                            "D:/gtamminga/workspace/ots-ntm/src/main/resources/gis/debug1/output/NTMoutputAccumulation.txt");
            dataAccumulationOut = createWriter(fileAccumulation);
            File fileLanesPerCell =
                    new File(
                            "D:/gtamminga/workspace/ots-ntm/src/main/resources/gis/debug1/output/NTMoutputLanesPerCell.txt");
            dataLanesPerCellOut = createWriter(fileLanesPerCell);
            File fileParametersFD =
                    new File(
                            "D:/gtamminga/workspace/ots-ntm/src/main/resources/gis/debug1/output/NTMoutputParametersFD.txt");
            dataParametersFDOut = createWriter(fileParametersFD);
        }

        if (model.DEBUG && steps < MAXSTEPS)
        {
            int i = 0;
            int linkNumber = 0;
            for (Link link : model.getDebugLinkList().values())
            {
                if (link.getBehaviourType() == TrafficBehaviourType.FLOW)
                {
                    LinkCellTransmission ctmLink = (LinkCellTransmission) link;
                    for (FlowCell cell : ctmLink.getCells())
                    {
                        if (steps == 1)
                        {
                            laneLength[i] = cell.getCellLength().getSI();
                            lanesPerCell[i] = (double) cell.getNumberOfLanes();
                            cellID[i] = String.valueOf(linkNumber);
                            parametersFD[i][0] =
                                    cell.getCellBehaviourFlow().getParametersFundamentalDiagram()
                                            .getMaxCapacityPerLane().doubleValue() * 3600;
                            parametersFD[i][1] =
                                    cell.getCellBehaviourFlow().getParametersFundamentalDiagram().getAccCritical()
                                            .get(0);
                            parametersFD[i][2] =
                                    cell.getCellBehaviourFlow().getParametersFundamentalDiagram().getAccCritical()
                                            .get(1);
                        }
                        accumulation[i][steps - 1] = cell.getCellBehaviourFlow().getAccumulatedCars();
                        i++;
                    }
                    linkNumber++;
                }
            }
            numberOfCells = i;
        }

        if (model.DEBUG && steps == MAXSTEPS - 1)
        {
            try
            {
                String textOut;
                for (int i = 0; i < numberOfCells; i++)
                {
                    textOut = String.format("%.1f", laneLength[i]);
                    dataLaneLengthOut.write(textOut + " \n");

                    textOut = String.format("%.1f", lanesPerCell[i]);
                    dataLanesPerCellOut.write(textOut + " \n");

                    textOut = String.format("%.1f", parametersFD[i][0]);
                    dataParametersFDOut.write(textOut + ", ");
                    textOut = String.format("%.1f", parametersFD[i][1]);
                    dataParametersFDOut.write(textOut + ", ");
                    textOut = String.format("%.1f", parametersFD[i][2]);
                    dataParametersFDOut.write(textOut + " \n");

                    dataAccumulationOut.write("Link " + cellID[i] + ", ");
                    for (int j = 0; j < steps; j++)
                    {
                        if (accumulation[i][j] > 0)
                        {
                            textOut = String.format("%.5f", accumulation[i][j]);
                        }
                        else
                        {
                            textOut = " NaN";
                        }
                        dataAccumulationOut.write(textOut + ", ");
                    }
                    dataAccumulationOut.write(" \n");
                }

                debugOut.close();
                dataLaneLengthOut.close();
                dataLanesPerCellOut.close();
                dataLaneLengthOut.close();
                dataParametersFDOut.close();
                dataAccumulationOut.close();
            }
            catch (IOException exception)
            {
                exception.printStackTrace();
            }
        }
    }

    private static BufferedWriter createWriter(File file)
    {
        // if file doesnt exists, then create it
        BufferedWriter bWriter = null;
        if (!file.exists())
        {
            try
            {
                file.createNewFile();
            }
            catch (IOException exception)
            {
                exception.printStackTrace();
            }
        }

        try
        {
            bWriter = new BufferedWriter(new FileWriter(file));
        }
        catch (IOException exception1)
        {
            exception1.printStackTrace();
        }
        return bWriter;
    }
}
