
namespace GMap.NET.MapProviders
{
   using System;
   using GMap.NET.Projections;
   using System.Globalization;
   using GMap.NET.Internals;
   using System.Collections.Generic;
   using System.Xml;
   using System.Diagnostics;

   public abstract class CloudMadeMapProviderBase : GMapProvider, RoutingProvider, DirectionsProvider
   {
      public readonly string ServerLetters = "abc";
      public readonly string DoubleResolutionString = "@2x";

      public bool DoubleResolution = true;
      public string Key;
      public int StyleID;

      public string Version = "0.3";

      public CloudMadeMapProviderBase()
      {
         MaxZoom = null;
      }

      #region GMapProvider Members
      public override Guid Id
      {
         get
         {
            throw new NotImplementedException();
         }
      }

      public override string Name
      {
         get
         {
            throw new NotImplementedException();
         }
      }

      public override PureProjection Projection
      {
         get
         {
            return MercatorProjection.Instance;
         }
      }

      GMapProvider[] overlays;
      public override GMapProvider[] Overlays
      {
         get
         {
            if(overlays == null)
            {
               overlays = new GMapProvider[] { this };
            }
            return overlays;
         }
      }

      public override PureImage GetTileImage(GPoint pos, int zoom)
      {
         throw new NotImplementedException();
      }
      #endregion

      #region RoutingProvider Members

      public MapRoute GetRoute(PointLatLng start, PointLatLng end, bool avoidHighways, bool walkingMode, int Zoom)
      {
         List<PointLatLng> points = GetRoutePoints(MakeRoutingUrl(start, end, walkingMode ? TravelTypeFoot : TravelTypeMotorCar, LanguageStr, "km"));
         MapRoute route = points != null ? new MapRoute(points, walkingMode ? WalkingStr : DrivingStr) : null;
         return route;
      }

      /// <summary>
      /// NotImplemented
      /// </summary>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="avoidHighways"></param>
      /// <param name="walkingMode"></param>
      /// <param name="Zoom"></param>
      /// <returns></returns>
      public MapRoute GetRoute(string start, string end, bool avoidHighways, bool walkingMode, int Zoom)
      {
         throw new NotImplementedException();
      }

      #region -- internals --

      string MakeRoutingUrl(PointLatLng start, PointLatLng end, string travelType, string language, string units)
      {
         // http://developers.cloudmade.com/projects/routing-http-api/examples/
         // http://routes.cloudmade.com/YOUR-API-KEY-GOES-HERE/api/0.3/start_point,[[transit_point1,...,transit_pointN]],end_point/route_type[/route_type_modifier].output_format[?lang=(en|de)][&units=(km|miles)]
         return string.Format(CultureInfo.InvariantCulture, UrlFormat, Key, Version, start.Lat, start.Lng, end.Lat, end.Lng, travelType, language, units);
      }

      List<PointLatLng> GetRoutePoints(string url)
      {
         List<PointLatLng> points = null;
         try
         {
            string route = GMaps.Instance.UseRouteCache ? Cache.Instance.GetContent(url, CacheType.RouteCache) : string.Empty;
            if(string.IsNullOrEmpty(route))
            {
               route = GetContentUsingHttp(url);
               if(!string.IsNullOrEmpty(route))
               {
                  if(GMaps.Instance.UseRouteCache)
                  {
                     Cache.Instance.SaveContent(url, CacheType.RouteCache, route);
                  }
               }
            }

            #region -- gpx response --
            //<?xml version="1.0" encoding="UTF-8"?>
            //<gpx creator="" version="1.1" xmlns="http://www.topografix.com/GPX/1/1"
            //    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            //    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 gpx.xsd ">
            //    <extensions>
            //        <distance>293</distance>
            //        <time>34</time>
            //        <start>Perckhoevelaan</start>
            //        <end>Goudenregenlaan</end>
            //    </extensions>
            //    <wpt lat="51.17702" lon="4.39630" />
            //    <wpt lat="51.17656" lon="4.39655" />
            //    <wpt lat="51.17639" lon="4.39670" />
            //    <wpt lat="51.17612" lon="4.39696" />
            //    <wpt lat="51.17640" lon="4.39767" />
            //    <wpt lat="51.17668" lon="4.39828" />
            //    <wpt lat="51.17628" lon="4.39874" />
            //    <wpt lat="51.17618" lon="4.39888" />
            //    <rte>
            //        <rtept lat="51.17702" lon="4.39630">
            //            <desc>Head south on Perckhoevelaan, 0.1 km</desc>
            //            <extensions>
            //                <distance>111</distance>
            //                <time>13</time>
            //                <offset>0</offset>
            //                <distance-text>0.1 km</distance-text>
            //                <direction>S</direction>
            //                <azimuth>160.6</azimuth>
            //            </extensions>
            //        </rtept>
            //        <rtept lat="51.17612" lon="4.39696">
            //            <desc>Turn left at Laarstraat, 0.1 km</desc>
            //            <extensions>
            //                <distance>112</distance>
            //                <time>13</time>
            //                <offset>3</offset>
            //                <distance-text>0.1 km</distance-text>
            //                <direction>NE</direction>
            //                <azimuth>58.1</azimuth>
            //                <turn>TL</turn>
            //                <turn-angle>269.0</turn-angle>
            //            </extensions>
            //        </rtept>
            //        <rtept lat="51.17668" lon="4.39828">
            //            <desc>Turn right at Goudenregenlaan, 70 m</desc>
            //            <extensions>
            //                <distance>70</distance>
            //                <time>8</time>
            //                <offset>5</offset>
            //                <distance-text>70 m</distance-text>
            //                <direction>SE</direction>
            //                <azimuth>143.4</azimuth>
            //                <turn>TR</turn>
            //                <turn-angle>89.8</turn-angle>
            //            </extensions>
            //        </rtept>
            //    </rte>
            //</gpx> 
            #endregion

            if(!string.IsNullOrEmpty(route))
            {
               XmlDocument xmldoc = new XmlDocument();
               xmldoc.LoadXml(route);
               System.Xml.XmlNamespaceManager xmlnsManager = new System.Xml.XmlNamespaceManager(xmldoc.NameTable);
               xmlnsManager.AddNamespace("sm", "http://www.topografix.com/GPX/1/1");

               XmlNodeList wpts = xmldoc.SelectNodes("/sm:gpx/sm:wpt", xmlnsManager);
               if(wpts != null && wpts.Count > 0)
               {
                  points = new List<PointLatLng>();
                  foreach(XmlNode w in wpts)
                  {
                     double lat = double.Parse(w.Attributes["lat"].InnerText, CultureInfo.InvariantCulture);
                     double lng = double.Parse(w.Attributes["lon"].InnerText, CultureInfo.InvariantCulture);
                     points.Add(new PointLatLng(lat, lng));
                  }
               }
            }
         }
         catch(Exception ex)
         {
            Debug.WriteLine("GetRoutePoints: " + ex);
         }

         return points;
      }

      static readonly string UrlFormat = "http://routes.cloudmade.com/{0}/api/{1}/{2},{3},{4},{5}/{6}.gpx?lang={7}&units={8}";
      static readonly string TravelTypeFoot = "foot";
      static readonly string TravelTypeMotorCar = "car";
      static readonly string WalkingStr = "Walking";
      static readonly string DrivingStr = "Driving";

      #endregion

      #endregion

      #region DirectionsProvider Members

      public DirectionsStatusCode GetDirections(out GDirections direction, PointLatLng start, PointLatLng end, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
      {
         return GetDirectionsUrl(MakeRoutingUrl(start, end, walkingMode ? TravelTypeFoot : TravelTypeMotorCar, LanguageStr, metric ? "km" : "miles"), out direction);
      }

      /// <summary>
      /// NotImplemented
      /// </summary>
      /// <param name="direction"></param>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="avoidHighways"></param>
      /// <param name="avoidTolls"></param>
      /// <param name="walkingMode"></param>
      /// <param name="sensor"></param>
      /// <param name="metric"></param>
      /// <returns></returns>
      public DirectionsStatusCode GetDirections(out GDirections direction, string start, string end, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// NotImplemented
      /// </summary>
      /// <param name="status"></param>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="avoidHighways"></param>
      /// <param name="avoidTolls"></param>
      /// <param name="walkingMode"></param>
      /// <param name="sensor"></param>
      /// <param name="metric"></param>
      /// <returns></returns>
      public IEnumerable<GDirections> GetDirections(out DirectionsStatusCode status, string start, string end, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// NotImplemented
      /// </summary>
      /// <param name="status"></param>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="avoidHighways"></param>
      /// <param name="avoidTolls"></param>
      /// <param name="walkingMode"></param>
      /// <param name="sensor"></param>
      /// <param name="metric"></param>
      /// <returns></returns>
      public IEnumerable<GDirections> GetDirections(out DirectionsStatusCode status, PointLatLng start, PointLatLng end, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
      {
         throw new NotImplementedException();
      }

      #region -- internals --

      DirectionsStatusCode GetDirectionsUrl(string url, out GDirections direction)
      {
         DirectionsStatusCode ret = DirectionsStatusCode.UNKNOWN_ERROR;
         direction = null;

         try
         {
            string route = GMaps.Instance.UseRouteCache ? Cache.Instance.GetContent(url, CacheType.DirectionsCache) : string.Empty;
            if(string.IsNullOrEmpty(route))
            {
               route = GetContentUsingHttp(url);
               if(!string.IsNullOrEmpty(route))
               {
                  if(GMaps.Instance.UseRouteCache)
                  {
                     Cache.Instance.SaveContent(url, CacheType.DirectionsCache, route);
                  }
               }
            }

            #region -- gpx response --
            //<?xml version="1.0" encoding="UTF-8"?>
            //<gpx creator="" version="1.1" xmlns="http://www.topografix.com/GPX/1/1"
            //    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            //    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 gpx.xsd ">
            //    <extensions>
            //        <distance>293</distance>
            //        <time>34</time>
            //        <start>Perckhoevelaan</start>
            //        <end>Goudenregenlaan</end>
            //    </extensions>
            //    <wpt lat="51.17702" lon="4.39630" />
            //    <wpt lat="51.17656" lon="4.39655" />
            //    <wpt lat="51.17639" lon="4.39670" />
            //    <wpt lat="51.17612" lon="4.39696" />
            //    <wpt lat="51.17640" lon="4.39767" />
            //    <wpt lat="51.17668" lon="4.39828" />
            //    <wpt lat="51.17628" lon="4.39874" />
            //    <wpt lat="51.17618" lon="4.39888" />
            //    <rte>
            //        <rtept lat="51.17702" lon="4.39630">
            //            <desc>Head south on Perckhoevelaan, 0.1 km</desc>
            //            <extensions>
            //                <distance>111</distance>
            //                <time>13</time>
            //                <offset>0</offset>
            //                <distance-text>0.1 km</distance-text>
            //                <direction>S</direction>
            //                <azimuth>160.6</azimuth>
            //            </extensions>
            //        </rtept>
            //        <rtept lat="51.17612" lon="4.39696">
            //            <desc>Turn left at Laarstraat, 0.1 km</desc>
            //            <extensions>
            //                <distance>112</distance>
            //                <time>13</time>
            //                <offset>3</offset>
            //                <distance-text>0.1 km</distance-text>
            //                <direction>NE</direction>
            //                <azimuth>58.1</azimuth>
            //                <turn>TL</turn>
            //                <turn-angle>269.0</turn-angle>
            //            </extensions>
            //        </rtept>
            //        <rtept lat="51.17668" lon="4.39828">
            //            <desc>Turn right at Goudenregenlaan, 70 m</desc>
            //            <extensions>
            //                <distance>70</distance>
            //                <time>8</time>
            //                <offset>5</offset>
            //                <distance-text>70 m</distance-text>
            //                <direction>SE</direction>
            //                <azimuth>143.4</azimuth>
            //                <turn>TR</turn>
            //                <turn-angle>89.8</turn-angle>
            //            </extensions>
            //        </rtept>
            //    </rte>
            //</gpx> 
            #endregion

            if(!string.IsNullOrEmpty(route))
            {
               XmlDocument xmldoc = new XmlDocument();
               xmldoc.LoadXml(route);
               System.Xml.XmlNamespaceManager xmlnsManager = new System.Xml.XmlNamespaceManager(xmldoc.NameTable);
               xmlnsManager.AddNamespace("sm", "http://www.topografix.com/GPX/1/1");

               XmlNodeList wpts = xmldoc.SelectNodes("/sm:gpx/sm:wpt", xmlnsManager);
               if(wpts != null && wpts.Count > 0)
               {
                  ret = DirectionsStatusCode.OK;

                  direction = new GDirections();
                  direction.Route = new List<PointLatLng>();

                  foreach(XmlNode w in wpts)
                  {
                     double lat = double.Parse(w.Attributes["lat"].InnerText, CultureInfo.InvariantCulture);
                     double lng = double.Parse(w.Attributes["lon"].InnerText, CultureInfo.InvariantCulture);
                     direction.Route.Add(new PointLatLng(lat, lng));
                  }

                  if(direction.Route.Count > 0)
                  {
                     direction.StartLocation = direction.Route[0];
                     direction.EndLocation = direction.Route[direction.Route.Count - 1];
                  }

                  XmlNode n = xmldoc.SelectSingleNode("/sm:gpx/sm:metadata/sm:copyright/sm:license", xmlnsManager);
                  if(n != null)
                  {
                     direction.Copyrights = n.InnerText;
                  }

                  n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:distance", xmlnsManager);
                  if(n != null)
                  {
                     direction.Distance = n.InnerText + "m";
                  }

                  n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:time", xmlnsManager);
                  if(n != null)
                  {
                     direction.Duration = n.InnerText + "s";
                  }

                  n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:start", xmlnsManager);
                  if(n != null)
                  {
                     direction.StartAddress = n.InnerText;
                  }

                  n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:end", xmlnsManager);
                  if(n != null)
                  {
                     direction.EndAddress = n.InnerText;
                  }

                  wpts = xmldoc.SelectNodes("/sm:gpx/sm:rte/sm:rtept", xmlnsManager);
                  if(wpts != null && wpts.Count > 0)
                  {
                     direction.Steps = new List<GDirectionStep>();

                     foreach(XmlNode w in wpts)
                     {
                        GDirectionStep step = new GDirectionStep();

                        double lat = double.Parse(w.Attributes["lat"].InnerText, CultureInfo.InvariantCulture);
                        double lng = double.Parse(w.Attributes["lon"].InnerText, CultureInfo.InvariantCulture);

                        step.StartLocation = new PointLatLng(lat, lng);

                        XmlNode nn = w.SelectSingleNode("sm:desc", xmlnsManager);
                        if(nn != null)
                        {
                           step.HtmlInstructions = nn.InnerText;
                        }

                        nn = w.SelectSingleNode("sm:extensions/sm:distance-text", xmlnsManager);
                        if(nn != null)
                        {
                           step.Distance = nn.InnerText;
                        }

                        nn = w.SelectSingleNode("sm:extensions/sm:time", xmlnsManager);
                        if(nn != null)
                        {
                           step.Duration = nn.InnerText + "s";
                        }

                        direction.Steps.Add(step);
                     }
                  }
               }
            }
         }
         catch(Exception ex)
         {
            ret = DirectionsStatusCode.ExceptionInCode;
            direction = null;
            Debug.WriteLine("GetDirectionsUrl: " + ex);
         }

         return ret;
      }

      #endregion

      #endregion
   }

   /// <summary>
   /// CloudMadeMap demo provider, http://maps.cloudmade.com/
   /// </summary>
   public class CloudMadeMapProvider : CloudMadeMapProviderBase
   {
      public static readonly CloudMadeMapProvider Instance;

      CloudMadeMapProvider()
      {
         Key = "5937c2bd907f4f4a92d8980a7c666ac0"; // demo key of CloudMade
         StyleID = 45363; // grab your style here http://maps.cloudmade.com/?styleId=45363
      }

      static CloudMadeMapProvider()
      {
         Instance = new CloudMadeMapProvider();
      }

      #region GMapProvider Members

      readonly Guid id = new Guid("00403A36-725F-4BC4-934F-BFC1C164D003");
      public override Guid Id
      {
         get
         {
            return id;
         }
      }

      readonly string name = "CloudMade, Demo";
      public override string Name
      {
         get
         {
            return name;
         }
      }

      public override PureImage GetTileImage(GPoint pos, int zoom)
      {
         string url = MakeTileImageUrl(pos, zoom, LanguageStr);

         return GetTileImageUsingHttp(url);
      }

      #endregion

      string MakeTileImageUrl(GPoint pos, int zoom, string language)
      {
         return string.Format(UrlFormat, ServerLetters[GetServerNum(pos, 3)], Key, StyleID, (DoubleResolution ? DoubleResolutionString : string.Empty), zoom, pos.X, pos.Y);
      }

      static readonly string UrlFormat = "http://{0}.tile.cloudmade.com/{1}/{2}{3}/256/{4}/{5}/{6}.png";
   }
}