/* *********************************************************************** *
 * project: org.matsim.*
 * *********************************************************************** *
 *                                                                         *
 * copyright       : (C) 2016 by the members listed in the COPYING,        *
 *                   LICENSE and WARRANTY file.                            *
 * email           : info at matsim dot org                                *
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *   See also COPYING, LICENSE and WARRANTY file                           *
 *                                                                         *
 * *********************************************************************** */

package org.matsim.pt2matsim.mapping.networkRouter;

import org.apache.log4j.Logger;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.Node;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.router.FastAStarLandmarksFactory;
import org.matsim.core.router.util.LeastCostPathCalculator;
import org.matsim.core.router.util.LeastCostPathCalculatorFactory;
import org.matsim.core.router.util.TravelDisutility;
import org.matsim.core.router.util.TravelTime;
import org.matsim.core.utils.collections.CollectionUtils;
import org.matsim.pt.transitSchedule.api.TransitLine;
import org.matsim.pt.transitSchedule.api.TransitRoute;
import org.matsim.pt.transitSchedule.api.TransitRouteStop;
import org.matsim.pt.transitSchedule.api.TransitSchedule;
import org.matsim.pt2matsim.config.OsmConverterConfigGroup;
import org.matsim.pt2matsim.config.PublicTransitMappingConfigGroup;
import org.matsim.pt2matsim.mapping.linkCandidateCreation.LinkCandidate;
import org.matsim.pt2matsim.tools.NetworkTools;
import org.matsim.pt2matsim.tools.PTMapperTools;
import org.matsim.utils.objectattributes.attributable.Attributes;
import org.matsim.vehicles.Vehicle;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author polettif
 */
public class ScheduleRoutersOsmAttributes implements ScheduleRouters {


    protected static Logger log = Logger.getLogger(ScheduleRoutersGtfsShapes.class);
    /**
     * If a link has a route with the same transport mode as the transit route,
     * the link's travel cost is multiplied by this factor.
     */
    private final double osmPtLinkTravelCostFactor;
    // standard fields
    private final TransitSchedule schedule;
    private final Network network;
    private final Map<String, Set<String>> transportModeAssignment;
    private final PublicTransitMappingConfigGroup.TravelCostType travelCostType;


    // path calculators
    private final Map<String, PathCalculator> pathCalculatorsByMode = new HashMap<>();
    private final Map<String, Network> networksByMode = new HashMap<>();
    private final Map<String, OsmRouter> osmRouters = new HashMap<>();


    public ScheduleRoutersOsmAttributes(TransitSchedule schedule, Network network, Map<String, Set<String>> transportModeAssignment, PublicTransitMappingConfigGroup.TravelCostType travelCostType, double osmPtLinkTravelCostFactor) {
        this.transportModeAssignment = transportModeAssignment;
        this.travelCostType = travelCostType;
        this.schedule = schedule;
        this.network = network;
        this.osmPtLinkTravelCostFactor = osmPtLinkTravelCostFactor;

        load();
    }

    /**
     * Load path calculators for all transit routes
     */
    private void load() {
        log.info("Initiating network and router for transit routes...");
        LeastCostPathCalculatorFactory factory = new FastAStarLandmarksFactory();
        for (TransitLine transitLine : schedule.getTransitLines().values()) {
            for (TransitRoute transitRoute : transitLine.getRoutes().values()) {
                String scheduleMode = transitRoute.getTransportMode();
                PathCalculator tmpRouter = pathCalculatorsByMode.get(scheduleMode);
                if (tmpRouter == null) {
                    log.info("New router for schedule mode " + scheduleMode);
                    Set<String> networkTransportModes = transportModeAssignment.get(scheduleMode);

                    Network filteredNetwork = NetworkTools.createFilteredNetworkByLinkMode(this.network, networkTransportModes);

                    OsmRouter r = new OsmRouter(scheduleMode);

                    tmpRouter = new PathCalculator(factory.createPathCalculator(filteredNetwork, r, r));

                    pathCalculatorsByMode.put(scheduleMode, tmpRouter);
                    networksByMode.put(scheduleMode, filteredNetwork);
                    osmRouters.put(scheduleMode, r);
                }
            }
        }
    }


    @Override
    public LeastCostPathCalculator.Path calcLeastCostPath(LinkCandidate fromLinkCandidate, LinkCandidate toLinkCandidate, TransitLine transitLine, TransitRoute transitRoute) {
        return this.calcLeastCostPath(fromLinkCandidate.getLink().getToNode().getId(), toLinkCandidate.getLink().getFromNode().getId(), transitLine, transitRoute);
    }

    @Override
    public LeastCostPathCalculator.Path calcLeastCostPath(Id<Node> fromNodeId, Id<Node> toNodeId, TransitLine transitLine, TransitRoute transitRoute) {
        Network n = networksByMode.get(transitRoute.getTransportMode());
        Node fromNode = n.getNodes().get(fromNodeId);
        Node toNode = n.getNodes().get(toNodeId);

        if (fromNode != null && toNode != null) {
            return pathCalculatorsByMode.get(transitRoute.getTransportMode()).calcPath(fromNode, toNode);
        } else {
            return null;
        }
    }

    @Override
    public double getMinimalTravelCost(TransitRouteStop fromTransitRouteStop, TransitRouteStop toTransitRouteStop, TransitLine transitLine, TransitRoute transitRoute) {
        return PTMapperTools.calcMinTravelCost(fromTransitRouteStop, toTransitRouteStop, travelCostType);
    }

    @Override
    public double getLinkCandidateTravelCost(LinkCandidate candidate) {
        return osmRouters.get(candidate.getStop().getTransitRoute().getTransportMode()).calcLinkTravelCost(candidate.getLink());
    }

    /**
     * Class is sent to path calculator factory
     */
    private class OsmRouter implements TravelDisutility, TravelTime {

        private final String scheduleMode;

        public OsmRouter(String scheduleMode) {
            this.scheduleMode = scheduleMode;
        }

        private double calcLinkTravelCost(Link link) {
            double travelCost = PTMapperTools.calcTravelCost(link, travelCostType);

            Attributes attributes = network.getLinks().get(link.getId()).getAttributes();
            Set<String> routeMaster = CollectionUtils.stringToSet((String) attributes.getAttribute(OsmConverterConfigGroup.LINK_ATTRIBUTE_RELATION_ROUTE_MASTER));
            Set<String> route = CollectionUtils.stringToSet((String) attributes.getAttribute(OsmConverterConfigGroup.LINK_ATTRIBUTE_RELATION_ROUTE));

            if (route.contains(scheduleMode) || routeMaster.contains(scheduleMode)) {
                travelCost *= osmPtLinkTravelCostFactor;
            }

            return travelCost;
        }

        @Override
        public double getLinkTravelDisutility(Link link, double time, Person person, Vehicle vehicle) {
            return calcLinkTravelCost(link);
        }

        @Override
        public double getLinkMinimumTravelDisutility(Link link) {
            return calcLinkTravelCost(link);
        }

        @Override
        public double getLinkTravelTime(Link link, double time, Person person, Vehicle vehicle) {
            return link.getLength() / link.getFreespeed();
        }
    }

    public static class Factory implements ScheduleRoutersFactory {
    	final private TransitSchedule schedule;
    	final private Network network;
    	final private Map<String, Set<String>> transportModeAssignment;
    	final private PublicTransitMappingConfigGroup.TravelCostType travelCostType;
    	final private double osmPtLinkTravelCostFactor;
    	
    	public Factory(TransitSchedule schedule, Network network, Map<String, Set<String>> transportModeAssignment, PublicTransitMappingConfigGroup.TravelCostType travelCostType, double osmPtLinkTravelCostFactor) {
    		this.schedule = schedule;
    		this.network = network;
    		this.transportModeAssignment = transportModeAssignment;
    		this.travelCostType = travelCostType;
    		this.osmPtLinkTravelCostFactor = osmPtLinkTravelCostFactor;
    	}

		@Override
		public ScheduleRouters createInstance() {
			return new ScheduleRoutersOsmAttributes(schedule, network, transportModeAssignment, travelCostType, osmPtLinkTravelCostFactor);
		}
    }
}