package cn.map.szmap.algorithm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import cn.base.util.RHUtil;
import cn.base.util.StringUtil;
import cn.map.shortroad.Point;
import cn.map.util.MapUtil.GaussSphere;

public class SzMapAlgoService {
	private static Log logger = LogFactory.getLog(SzMapAlgoService.class);
	
	private static final int MAP_SCALE = 10; // 平面划分粒度
	
	/**
	 * 点选
	 * @param point
	 * @param width
	 * @param channelList
	 * @return
	 */
	public static List<Map> getListByPoint(Point point, double width, List<Map> channelList) {
		List<ComparableMap> list = null;
		try {
			List<Map> resultList = getListByPerpendicular(point, channelList, width);
			list = toComparable(resultList);
			ComparableMap[] arr = list.toArray(new ComparableMap[0]);
			Arrays.sort(arr);
			list = Arrays.asList(arr);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return toMap(list);
	}
	
	/**
	 * 框选
	 * @param pointList
	 * @param channelList
	 * @param doExcel
	 * @return
	 */
	public static List<Map> getListByRec(List<Point> pointList, List<Map> channelList, boolean doExcel, int limit) {
		List<Map> resultList = null;
		try {
			Point a = pointList.get(0);
			Point b = pointList.get(1);
			resultList = getListByRec(a.getX(), a.getY(), b.getX(), b.getY(), channelList, limit);
			long l = (long) (b.getY() - a.getY());
			if (l < (b.getX() - a.getX()))
				l = (long) (b.getX() - a.getX());
			if (doExcel && resultList.size() > MAP_SCALE * MAP_SCALE) {
				resultList = releaseNodes(a.getX().longValue(), a.getY().longValue(), l, resultList);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
	
	/**
	 * 线选
	 * @param pointList
	 * @param width
	 * @param channelList
	 * @return
	 */
	public static List<Map> getListByLine(List<Point> pointList, double width, List<Map> channelList) {
		List<Map> resultList = null;
		try {
			resultList = getListByPerpendicular(pointList, channelList, width);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
	
	/**
	 * 
	 * @param pointList
	 * @param channelList
	 * @param doExcel
	 * @return
	 */
	public static List<Map> getListByCircle(List<Point> pointList, List<Map> channelList, boolean doExcel) {
		List<Map> resultList = null;
		try {
			Point a = pointList.get(0);
			Point b = pointList.get(1);
			double r = (b.getY() - a.getY()) / 2.0;
			resultList = getListByCircle(a.getX() + r, a.getY() + r, r, channelList);
			if (doExcel && resultList.size() > MAP_SCALE * MAP_SCALE) {
				resultList = releaseNodes(a.getX().longValue(), a.getY().longValue(), r * 2, resultList);
			}
		}  catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
	
	private static List<Map> getListByPerpendicular(Point point, List<Map> channelList, double width) {
		List<Map> resultList = new ArrayList<Map>();
		try {
			for (int j = 0; j < channelList.size(); j++) {
				double lng = Double.parseDouble(RHUtil.isNotEmpty(channelList.get(j).get("longitude")) ? channelList.get(j).get("longitude").toString() : "0");
				double lat = Double.parseDouble(RHUtil.isNotEmpty(channelList.get(j).get("latitude")) ? channelList.get(j).get("latitude").toString() : "0");
				double dis = distanceOfTwoPoints(point.getX(), point.getY(), lng, lat, GaussSphere.Xian80);
				if (dis <= (double) width) {
					double angle = angleOfTwoPoints(point.getX(), point.getY(), lng, lat);
					int position = (int) (angle - 22.5) / 45 + 2;
					if ((angle >= 0 && angle < 22.5) || (angle >= 337.5 && angle <= 360)) {
						position = 1;
					}
					if (point.getX() == lng && point.getY() == lat) {
						position = 0;
						angle = 0;
					}
					Map channel = channelList.get(j);
					channel.put("DISTANCE", dis);
					channel.put("ANGLE", Math.floor(angle));
					channel.put("POSITION", position);
					channel.put(ComparableMap.SORT_KEY, dis);
					resultList.add(channel);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
	
	public static double distanceOfTwoPoints(double lng1, double lat1, double lng2, double lat2, GaussSphere gs) {
		double s = 0;
		try {
			double radLat1 = radius(lat1 / 1000000.0);
			double radLat2 = radius(lat2 / 1000000.0);
			double a = radLat1 - radLat2;
			double b = radius(lng1 / 1000000.0) - radius(lng2 / 1000000.0);
			s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2)
					* Math.pow(Math.sin(b / 2), 2)));
			s = s * (gs == GaussSphere.WGS84 ? 6378137.0 : (gs == GaussSphere.Xian80 ? 6378140.0 : 6378245.0));
			s = Math.round(s * 10000) / 10000d;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return s;
	}
	
	private static double radius(double d) {
		return d * Math.PI / 180.0;
	}
	
	public static List<ComparableMap> toComparable(List<Map> list) {
		List<ComparableMap> result = new ArrayList<ComparableMap>();
		for (Map map : list) {
			result.add(new ComparableMap(map));
		}
		return result;
	}
	
	public static double angleOfTwoPoints(double lng1, double lat1, double lng2, double lat2) {
		double angle = (Math.atan((lat2 - lat1) / (lng2 - lng1))) * 180 / Math.PI;
		if (lng2 < lng1) {
			angle = angle + 180;
		} else if (lat2 < lat1) {
			angle = angle + 360;
		}
		return angle;
	}
	
	public static List<Map> toMap(List<ComparableMap> list) {
		List<Map> result = new ArrayList<Map>();
		for (Map map : list) {
			result.add(new HashMap(map));
		}
		return result;
	}
	
	public static class ComparableMap extends HashMap implements Comparable<ComparableMap> {
		public static final String SORT_KEY = "SORT_KEY";
		
		public ComparableMap(Map map) {
			super(map);
		}
		
		public int compareTo(ComparableMap o) {
			if(this.get(SORT_KEY)!=null&&o.get(SORT_KEY)!=null){
				double me = (Double.parseDouble(this.get(SORT_KEY).toString()));
				double other = (Double.parseDouble(o.get(SORT_KEY).toString()));
				if(me>other){
					return 1;
				}else if( me<other){
					return -1;
				}
			}
			return 0;
		}

	}
	
	private static List<Map> getListByRec(double x, double y, double x2, double y2, List<Map> list, int limit) {
		List<Map> resultList = new ArrayList<Map>();
		try {
			for (int i = 0,m = 0; i < list.size(); i++) {
				double lng = getLng(list.get(i));
				double lat = getLat(list.get(i));
				if (x < lng && lng < x2 && y < lat && lat < y2) {
					m++;
					resultList.add(list.get(i));
				}
				if (limit > 0) {
					if ((m+1) == limit) 
						break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
	
	private static double getLng(Map map) {
		Object obj = map.get("longitude");
		if (obj != null && RHUtil.isNotEmpty(obj)) {
			return Double.parseDouble(obj.toString());
		}
		return 0;
	}

	private static double getLat(Map map) {
		Object obj = map.get("latitude");
		if (obj != null && RHUtil.isNotEmpty(obj)) {
			return Double.parseDouble(obj.toString());
		}
		return 0;
	}
	
	private static List<Map> releaseNodes(long x, long y, double L, List<Map> resultList) {
		List<Map> result = new ArrayList<Map>();
		int[][] nn = new int[MAP_SCALE][MAP_SCALE];
		for (int i = 0; i < nn.length; i++) {
			for (int j = 0; j < nn[i].length; j++) {
				nn[i][j] = 0;
			}
		}
		double l = L / MAP_SCALE;
		for (Map chan : resultList) {
			int i = ((Double) ((getLng(chan) - x) / l)).intValue();
			int j = ((Double) ((getLat(chan) - y) / l)).intValue();
			if (nn[i][j] == 0) {
				result.add(chan);
				nn[i][j] = 1;
			}
		}
		return result;
	}
	
	private static List<Map> getListByPerpendicular(List<Point> pointList, List<Map> channelList, double width) {
		List<Map> resultList = new ArrayList<Map>();
		try {
			double abs[] = new double[pointList.size() - 1];
			for (int i = 0; i < pointList.size() - 1; i++) {
				Point a = pointList.get(i);
				Point b = pointList.get(i + 1);
				abs[i] = distanceOfTwoPoints(a.getX(), a.getY(), b.getX(), b.getY(), GaussSphere.Xian80);
			}
			for (int j = 0; j < channelList.size(); j++) {
				double lng = Double.parseDouble(RHUtil.isNotEmpty(channelList.get(j).get("longitude")) ? channelList.get(j).get("longitude").toString() : "0");
				double lat = Double.parseDouble(RHUtil.isNotEmpty(channelList.get(j).get("latitude")) ? channelList.get(j).get("latitude").toString() : "0");
				for (int i = 0; i < pointList.size() - 1; i++) {
					Point a = pointList.get(i);
					Point b = pointList.get(i + 1);
					double dis2Point = distanceOfTwoPoints(a.getX(), a.getY(), lng, lat, GaussSphere.Xian80);
					if (dis2Point <= (double) width) {
						Map channel = channelList.get(j);
						resultList.add(channel);
						break;
					}
					if (i == pointList.size() - 2) {
						double dis2Pointb = distanceOfTwoPoints(b.getX(), b.getY(), lng, lat, GaussSphere.Xian80);
						if (dis2Pointb <= (double) width) {
							Map channel = channelList.get(j);
							resultList.add(channel);
							break;
						}
					}
	
					double ab = abs[i];
					double ac = distanceOfTwoPoints(a.getX(), a.getY(), lng, lat, GaussSphere.Xian80);
					double bc = distanceOfTwoPoints(b.getX(), b.getY(), lng, lat, GaussSphere.Xian80);
					if ((ac * ac - ab * ab - bc * bc) <= 0 && (bc * bc - ab * ab - ac * ac) <= 0) {
						double s = (ab + bc + ac) / 2;
						double h = 0;
						if (ac + bc > ab) {
							h = Math.sqrt(s * (s - ab) * (s - bc) * (s - ac)) * 2 / ab;
						}
						if (h <= (double) width) {
							Map channel = channelList.get(j);
							resultList.add(channel);
							break;
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
	
	public static List<Map> getListByCircle(double x, double y, double r, List<Map> list) {
		List<Map> resultList = new ArrayList<Map>();
		try {
			double rr = r * r;
			for (int i = 0; i < list.size(); i++) {
				double lng = getLng(list.get(i));
				double lat = getLat(list.get(i));
				if ((x - lng) * (x - lng) + (y - lat) * (y - lat) <= rr) {
					resultList.add(list.get(i));
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultList;
	}
	
	/**
	 * 获取当前页面各个块的点
	 * @param map 包含4个key:minLng, minLat, maxLng, maxLat
	 * @return
	 */
	public static List<Map> getMapPointsOf32Blocks( Map map ) {
		List<Map> list = new ArrayList<Map>();
		try {
			double minLng = Double.parseDouble((String)map.get("minLng"));
			double minLat = Double.parseDouble((String)map.get("minLat"));
			double maxLng = Double.parseDouble((String)map.get("maxLng"));
			double maxLat = Double.parseDouble((String)map.get("maxLat"));
			final int cols = 8;
			final int rows = 4;
			int accuracy = 1000000;//精度
			double lngMinUnit = Math.round(((maxLng - minLng) / cols * accuracy)) / (double)accuracy;
			double latMinUnit = Math.round(((maxLat - minLat) / rows * accuracy)) / (double)accuracy;
			for(int i = 0; i < cols; i++ ) {
				Map<String,String>  map2 = new HashMap<String,String>();
				double minLng2 = Math.round((minLng + i * lngMinUnit) * accuracy) / (double)accuracy;
				double minLat2 = Math.round((maxLat - latMinUnit) * accuracy) / (double)accuracy;  
				double maxLng2 = Math.round((minLng2 + lngMinUnit) * accuracy) / (double)accuracy;
				double maxLat2 = Math.round(maxLat *  accuracy) / (double)accuracy;
				map2.put("minLng",String.valueOf(minLng2));
				map2.put("minLat",String.valueOf(minLat2));
				map2.put("maxLng",String.valueOf(maxLng2));
				map2.put("maxLat",String.valueOf(maxLat2));
				list.add(map2);
				for(int j = 0; j < rows - 1; j++ ) {
					map2 = new HashMap<String,String>();
					map2.put("minLng",String.valueOf(minLng2));
					map2.put("minLat",String.valueOf(Math.round((minLat2 - (j + 1) * latMinUnit) * accuracy) / (double)accuracy));
					map2.put("maxLng",String.valueOf(maxLng2));
					map2.put("maxLat",String.valueOf(Math.round((maxLat2 - (j + 1) * latMinUnit) * accuracy) / (double)accuracy));
					list.add(map2);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}
	
	public static  boolean isPointInCurrentRange(double x , double y , Point pointMin , Point pointMax) {
		double minLng =  pointMin.getX();
		double minLat =  pointMin.getY();
		double maxLng =  pointMax.getX();
		double maxLat =  pointMax.getY();
		return (minLng < x && x < maxLng && minLat < y && y < maxLat);  
	}
	
}
