package com.cc.infosur.routing;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.osmdroid.api.IGeoPoint;
import org.osmdroid.util.GeoPoint;

import com.cc.infosur.greendao.DaoMaster;
import com.cc.infosur.greendao.DaoSession;
import com.cc.infosur.greendao.Link;
import com.cc.infosur.greendao.LinkDao;
import com.cc.infosur.greendao.Node;
import com.cc.infosur.greendao.NodeDao;
import com.cc.infosur.greendao.DaoMaster.DevOpenHelper;
import com.cc.infosur.greendao.NodeDao.Properties;

import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.QueryBuilder;
import de.greenrobot.dao.query.WhereCondition;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

public class Network {
	/**
		 * 
		 */
	// public HashMap<Long, Node> nodes;
	// public HashMap<String, Link> links;
	// long nodeNumber = 0;

	private SQLiteDatabase db;
	private DevOpenHelper helper;
	private DaoSession daoSession;
	private DaoMaster daoMaster;
	private NodeDao nodeDao;
	private LinkDao linkDao;
//	private long linkId;

	public Network(Context context) {
		// nodes = new HashMap<Long, Node>();
		// links = new HashMap<String, Link>();
		// nodeNumber = 0;
		// Prepare db
		helper = new DaoMaster.DevOpenHelper(context, "tripdb", null);
		db = helper.getWritableDatabase();
		daoMaster = new DaoMaster(db);
		daoSession = daoMaster.newSession();
		nodeDao = daoSession.getNodeDao();
		linkDao = daoSession.getLinkDao();
//		linkId = linkDao.count() + 1000;
	}
	
	public long getNumberOfNodes(){
		return nodeDao.count();
	}

	public Node getNode(long id) {
		return nodeDao.load(id);
	}

	public Link getLink(long id) {
		return linkDao.load(id);
	}

	public long createLink(double length, double speed, Node nodeFrom,
			Node nodeTo) {
		Link link = new Link();
//		link.setId(linkId++);
		link.setLength(length);
		link.setSpeed(speed);
		link.setNodeFrom(nodeFrom);
		link.setNodeTo(nodeTo);
		linkDao.insert(link);
		nodeDao.update(nodeFrom);
//		Log.i("PATH",
//				"CREATE LINK ID: " + link.getId() + " (" + nodeFrom.getId()
//						+ "->" + nodeTo.getId() + ")");
		return link.getId();
	}

	public double getLengthToEndOfLink(Link link, Node node, boolean forward) {
		double length = 0;

		List<Node> nodes;
		if (forward)
			nodes = nodeDao
					.queryBuilder()
					.where(Properties.LinkForward.eq(link.getId()),
							Properties.PositionForward.gt(node
									.getPositionForward()))
					.orderAsc(Properties.PositionForward).list();
		else
			nodes = nodeDao
					.queryBuilder()
					.where(Properties.LinkBackward.eq(link.getId()),
							Properties.PositionBackward.gt(node
									.getPositionBackward()))
					.orderAsc(Properties.PositionBackward).list();

		if (nodes.size() > 0) {
			for (Node auxNode : nodes) {
				length += computeDist(node.getLatitude(), node.getLongitude(),
						auxNode.getLatitude(), auxNode.getLongitude());
				node = auxNode;
			}
			length += computeDist(node.getLatitude(), node.getLongitude(), link
					.getNodeTo().getLatitude(), link.getNodeTo().getLongitude());
		} else {
			length = computeDist(node.getLatitude(), node.getLongitude(), link
					.getNodeTo().getLatitude(), link.getNodeTo().getLongitude());
		}

		return length;
	}

	// @Override
	// public String toString() {
	// return "nodes:" + nodes.values().size() + "\tlinks:"
	// + links.values().size();
	// }

	public Node getClosest(double latitude, double longitude) {
		Node closest = null;
		double dist = Double.MAX_VALUE;
		for (Node n : nodeDao.loadAll()) {
			double d = computeDist(latitude, longitude, n.getLatitude(),
					n.getLongitude());
			if (closest == null || d < dist) {
				dist = d;
				closest = n;
			}
		}
		return closest;
	}

	public double getDistanceToClosestNode(double latitude, double longitude) {
		Node closest = null;
		double dist = Double.MAX_VALUE;
		for (Node n : nodeDao.loadAll()) {
			double d = computeDist(latitude, longitude, n.getLatitude(),
					n.getLongitude());
			if (closest == null || d < dist) {
				dist = d;
				closest = n;
			}
		}
		return dist;
	}

	public static double computeDist(double lat_a, double lon_a, double lat_b,
			double lon_b) {
		double a = Math.PI / 180;
		double lat1 = lat_a * a;
		double lat2 = lat_b * a;
		double lon1 = lon_a * a;
		double lon2 = lon_b * a;
		double t1 = Math.sin(lat1) * Math.sin(lat2);
		double t2 = Math.cos(lat1) * Math.cos(lat2);
		double t3 = Math.cos(lon1 - lon2);
		double t4 = t2 * t3;
		double t5 = t1 + t4;
		double rad_dist = Math.atan(-t5 / Math.sqrt(-t5 * t5 + 1)) + 2
				* Math.atan(1);
		return (rad_dist * 3437.74677 * 1.1508) * 1609.3470878864446;
	}

	public void newLinks(Link originalLink, Node node, boolean forward) {

		double lengthToEnd;
		double lengthToStart;
		long newLinkId;
		
		// Calculate the lengths of the new links
		lengthToEnd = getLengthToEndOfLink(originalLink, node, forward);
		lengthToStart = originalLink.getLength() - lengthToEnd;
		// Create the first link and update nodes
		newLinkId = createLink(lengthToStart, originalLink.getSpeed(),
				originalLink.getNodeFrom(), node);
		if (forward)
			updateNodesToNewLink(newLinkId,
					Properties.LinkForward.eq(originalLink.getId()),
					Properties.PositionForward.lt(node.getPositionForward()),
					Properties.PositionForward, true);
		else
			updateNodesToNewLink(newLinkId,
					Properties.LinkBackward.eq(originalLink.getId()),
					Properties.PositionBackward.lt(node.getPositionBackward()),
					Properties.PositionBackward, false);
		
		// Create the second link and update nodes
		newLinkId = createLink(lengthToEnd, originalLink.getSpeed(), node,
				originalLink.getNodeTo());
		if (forward)
			updateNodesToNewLink(newLinkId,
					Properties.LinkForward.eq(originalLink.getId()),
					Properties.PositionForward.gt(node.getPositionForward()),
					Properties.PositionForward, true);
		else
			updateNodesToNewLink(newLinkId,
					Properties.LinkBackward.eq(originalLink.getId()),
					Properties.PositionBackward.gt(node.getPositionBackward()),
					Properties.PositionBackward, false);
		
		
		if (forward)
			node.setLinkForward(0L);
		else
			node.setLinkBackward(0L);
		
		nodeDao.update(node);

		// Delete the old link
		linkDao.delete(originalLink);
	}

	public void updateNodesToNewLink(long newLinkId,
			WhereCondition linkIdCondition, WhereCondition positionCondition,
			Property orderProperty, boolean forward) {
		List<Node> nodes;
		nodes = nodeDao.queryBuilder()
				.where(linkIdCondition, positionCondition)
				.orderAsc(orderProperty).list();

//		Log.i("Route", "updateNodestonewlink: number of nodes: " + nodes.size());
		if (forward) {
			for (int i = 0; i < nodes.size(); i++) {
				nodes.get(i).setLinkForward(newLinkId);
				nodes.get(i).setPositionForward(i+1);
				nodeDao.update(nodes.get(i));
			}
		} else {
			for (int i = 0; i < nodes.size(); i++) {
				nodes.get(i).setLinkBackward(newLinkId);
				nodes.get(i).setPositionBackward(i+1);
				nodeDao.update(nodes.get(i));
			}
		}

	}

	public List<IGeoPoint> calculateRoute(double latitudeOrigin,
			double longitudeOrigin, double latitudeDest, double longitudeDest) {
		// Check closest node to origin and destination and make temporary link
		// if necessary
		Log.i("ROUTE", "origin: " + latitudeOrigin + " " + longitudeOrigin);


        System.out.println(" NODE SIZE :: " + nodeDao.loadAll().size() );
        System.out.println(" LINK SIZE :: " + linkDao.loadAll().size() );

        if(nodeDao.loadAll().size() == 0 && linkDao.loadAll().size() == 0) {
            System.out.println(" NO ROADS ");
        }

		Node nodeFrom = getClosest(latitudeOrigin, longitudeOrigin);
		List<IGeoPoint> path = new ArrayList<IGeoPoint>();

		if (nodeFrom.getLinkForward() != 0L) {
			Link link = getLink(nodeFrom.getLinkForward());
			newLinks(link, nodeFrom, true);
		}
		if (nodeFrom.getLinkBackward() != 0L) {
			Link link = getLink(nodeFrom.getLinkBackward());
			newLinks(link, nodeFrom, false);
		}

		Node nodeTo = getClosest(latitudeDest, longitudeDest);
		if (nodeTo.getLinkForward() != 0L) {
			Link link = getLink(nodeTo.getLinkForward());
			newLinks(link, nodeTo, true);
		}
		if (nodeTo.getLinkBackward() != 0L) {
			Link link = getLink(nodeTo.getLinkBackward());
			newLinks(link, nodeTo, false);
		}
		
		List<Link> links = findShortestPath(nodeFrom, nodeTo);

//		Log.i("PATH", "Links");
		if (links != null && links.size() > 0) {
			// Add the first node of the path
			path.add(new GeoPoint(links.get(0).getNodeFrom().getLatitude(),
					links.get(0).getNodeFrom().getLongitude()));

		Log.i("Route", "number of links: " + links.size());

			for (Link link : links) {
//				Log.i("ROUTE", "Link " + link.getId() + ": " + link.getNodeFromId() + " -> " + link.getNodeToId());

				QueryBuilder<Node> qb = nodeDao.queryBuilder();
				qb.where(Properties.LinkForward.eq(link.getId()));
				List<Node> nodes = qb.orderAsc(Properties.PositionForward)
						.list();

				if (nodes.size() == 0) {
					qb = nodeDao.queryBuilder();
					qb.where(Properties.LinkBackward.eq(link.getId()));
					nodes = qb.orderAsc(Properties.PositionBackward).list();
				}

				for (Node node : nodes) {
					path.add(new GeoPoint(node.getLatitude(), node
							.getLongitude()));
				}

				path.add(new GeoPoint(link.getNodeTo().getLatitude(), link
						.getNodeTo().getLongitude()));
			}
		}
		
		Log.i("Route", "number of points: " + path.size());

		return path;
	}

	// public double getDistance(double sx, double sy, double dx, double dy) {
	// Node from = getClosest(sx, sy);
	// Node to = getClosest(dx, dy);
	// Path p = findShortestPath(from, to);
	//
	// if (p != null) {
	// return p.distance;
	// } else {
	// return -1;
	// }
	// }

	public List<Link> findShortestPath(Node source, Node destination) {
		
//		Log.i("PATH", "from " + source.getId() + " to " + destination.getId());

		List<Link> path;

		List<Node> nodes = nodeDao.queryBuilder()
				.where(Properties.LinkForward.eq(0L)).list();

//		Log.i("ROUTE", "number of nodes.....:" + nodes.size());
		for (Node node : nodes) {
			node.setDistance(Double.MAX_VALUE);
			node.setPrevious(null);
		}

		source.setDistance((double) 0);
		LinkedList<Node> q = new LinkedList<Node>(nodes);
		while (!q.isEmpty()) {
			Node u = null;
			for (Node n : q) {
				if (u == null || u.getDistance() > n.getDistance()) {
					u = n;
				}
			}

			if (u == destination) {
				// destination is reached --- > out
				break;
			}

			q.remove(u);
			if (u.getDistance() == Double.MAX_VALUE) {
				break;
			}

			for (Link neighbor : u.getOuts()) {
				Node v = neighbor.getNodeTo();
				double dist = u.getDistance()
						+ (neighbor.getLength() / neighbor.getSpeed());
//				 Log.i("PATH", v.getId() + "dist: " + v.getDistance() + " " + v.getLinkForward() +  " " + v.getLinkBackward() + " LL: " + v.getLatitude() + " " + v.getLongitude());
				if (v.getDistance() == null)
					continue;
				if (dist < v.getDistance()) {
					v.setDistance(dist);
					v.setPrevious(neighbor);
				}
			}
		}

		if (destination.getDistance() == Double.MAX_VALUE) {
			Log.i("PATH", "MAX_DISTANCE");
			return null;
		} else {
			path = new ArrayList<Link>();
			LinkedList<Link> tmp = new LinkedList<Link>();
			Node n = destination;
			do {
				Link l = n.getPrevious();
				tmp.add(0, l);
				n = l.getNodeFrom();
			} while (n != source);

			for (Link l : tmp) {
				path.add(l);
			}
			return path;
		}
	}

}
