import ceylon.time {
	Period,
	zeroPeriod=zero
}

import graph {
	IncidenceGraph,
	Walk,
	Weights
}
import graph.explicit {
	AbstractIncidenceList
}
import graph.multigraph {
	Multigraph
}
import graph.undirected {
	UndirectedEdge,
	UndirectedGraph
}

"Convenience function to create periods from hours and minutes only."
by ("ThorstenSeitz")
shared Period period(Integer hours, Integer minutes) => Period(0, 0, 0, hours, minutes, 0, 0);

"Convenience function to create a [[Distance]]."
by ("ThorstenSeitz")
shared Distance km(Integer kilometers, Integer meters = 0) => Distance(kilometers * 1000 + meters);

"Distance measured in km (Value Object)."
by ("ThorstenSeitz")
shared final class Distance(shared Integer meters) satisfies Summable<Distance> & Comparable<Distance> {
	shared actual Distance plus(Distance other) => Distance(meters + other.meters);
	shared actual Comparison compare(Distance other) => meters.compare(other.meters);
	shared actual Boolean equals(Object other) { // TODO: rewrite as if expression in next Ceylon version
		if (is Distance other) {
			return meters.equals(other.meters);
		} else {
			return false;
		}
	}
	shared actual Integer hash => meters.hash;
	shared actual String string => "``meters`` m";
}

"A city (Value Object)."
by ("ThorstenSeitz")
shared final class City(shared String name) satisfies Comparable<City> {
	shared actual Comparison compare(City other) => name.compare(other.name);
	shared actual Boolean equals(Object other) { // TODO: rewrite as if expression in next Ceylon version
		if (is City other) {
			return name.equals(other.name);
		} else {
			return false;
		}
	}
	shared actual Integer hash => name.hash;
	shared actual String string => name;
}

"A route between two cities."
by ("ThorstenSeitz")
shared final class Route(
	shared String name,
	shared actual City source,
	shared actual City target,
	shared Distance distance,
	shared Period travelTime) satisfies UndirectedEdge<City,Route> {

	shared actual Route reversed => Route(name, target, source, distance, travelTime);
}

"A route map of [[cities|City]] connected by undirected [[routes|Route]] with a given [[distance|Distance]]
 and [[travel time|ceylon.time::Period]].
 The same cities may be connected by multiple routes (e.g. Autobahn or Bundesstraße) with different distances
 and travel times. The route map is therefore a [[Multigraph]]."
by ("ThorstenSeitz")
shared final class RouteMap({City*} cities, {Route*} routes)
		extends AbstractIncidenceList<City,Route,RouteMap>(cities, routes)
		satisfies Multigraph<City,Route> & IncidenceGraph<City,Route> & UndirectedGraph<City,Route> {

	"An accessor for the [[distance|Route.distance]] of a route."
	shared object distances satisfies Weights<Distance,City,Route> {
		shared actual Distance weight(Route route) => route.distance;
		shared actual Distance zero => km(0);
	}

	"An accessor for the [[travel time|Route.travelTime]] of a route."
	shared object travelTimes satisfies Weights<Period,City,Route> {
		shared actual Period weight(Route route) => route.travelTime;
		shared actual Period zero => zeroPeriod;
	}

	"Answer all routes between the given cities."
	shared actual {Route*} edgesConnecting(City source, City target) => adjacentEdges(source).filter((Route route) => route.target == target);

	"Create RouteMap from vertices and edges (used by filter methods)."
	shared actual RouteMap create({City*} vertices, {Route*} edges) => RouteMap(vertices, edges);
}

"This example combines a concrete [[RouteMap]] with a named list of the cities forming its vertices."
shared object example {
	shared City leipzig = City("Leipzig");
	shared City hamburg = City("Hamburg");
	shared City berlin = City("Berlin");
	shared City hannover = City("Hannover");
	shared City kassel = City("Kassel");
	shared City wuerzburg = City("Würzburg");
	shared RouteMap routeMap = RouteMap(
		{ berlin, hamburg, hannover, kassel, leipzig, wuerzburg },
		{
			Route("A7", hamburg, hannover, km(151), period(1, 38)),
			Route("B3", hamburg, hannover, km(159), period(3, 15)),
			Route("A2", hannover, berlin, km(285), period(3, 4)),
			Route("B188", hannover, berlin, km(292), period(5, 4)),
			Route("A24", hamburg, berlin, km(288), period(2, 54)),
			Route("A9", berlin, leipzig, km(190), period(2, 11)),
			Route("B2", berlin, leipzig, km(174), period(3, 31)),
			Route("B79", hannover, leipzig, km(263), period(4, 38)),
			Route("A38", leipzig, kassel, km(254), period(2, 26)),
			Route("A4", leipzig, kassel, km(287), period(3, 23)),
			Route("B249", leipzig, kassel, km(258), period(4, 34)),
			Route("A7", hannover, kassel, km(167), period(1, 54)),
			Route("B3", hannover, kassel, km(156), period(2, 44)),
			Route("A7", wuerzburg, kassel, km(210), period(1, 56))
		});
}

"Example code demonstrating how to calculate different distances on a path or [[walk|Walk]]."
shared void printPathInfo(RouteMap routeMap, Walk<City,Route> shortestPath) {

	Distance distance = shortestPath.distance(routeMap.distances);
	Period travelTime = shortestPath.distance(routeMap.travelTimes);

	print("Distance = ``distance``");
	print("Travel time = ``travelTime``");
}