static void showResult(string[] orig, DistanceResults distances)
        {
            Console.WriteLine("Searched:\t{0}", string.Join(" | ", orig));
            Console.WriteLine("Status:\t{0}", distances.Status);
            Console.WriteLine("Locations:\t{0}", string.Join(" | ", distances.Origin));

            int j = 0;

            foreach (var row in distances.Row)
            {
                int i = 0;
                foreach (var col in row)
                {
                    Console.WriteLine("Path:\t{0}...{1}", distances.Origin[i], distances.Destination[j]);
                    if (col != null)
                    {
                        Console.WriteLine("\tDuration:\t{0}", col.DurationText);
                        Console.WriteLine("\tDistance:\t{0}", col.DistanceText);
                    }
                    else
                    {
                        Console.WriteLine("Data not found.  Check the name of the location.  Chnage it to a land address.");
                    }
                    i++;
                }
                j++;
            }
        }
        public void WriteXML(DistanceResults store, string f)
        {
            XmlSerializer writer = new XmlSerializer(typeof(DistanceResults));

            var path = f;

            using (FileStream file = System.IO.File.Create(path))
            {
                writer.Serialize(file, store);
                file.Close();
            }
        }
        public DistanceResults getCachedDistances(string[] places)
        {
            string cachefile = GetFilenameFor(places, places);

            if (File.Exists(cachefile) && DateTime.Now - (new FileInfo(cachefile)).LastWriteTime < __30_DAYS)
            {
                try
                {
                    return(ReadXML(cachefile));
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                    File.Delete(cachefile);
                }
            }

            DistanceResults result = getDistancesFromGoogle(places);

            WriteXML(result, cachefile);

            return(result);
        }
        //we want a system of ordering the requests to make best use of caching... order the requests by origin and destination
        public DistanceResults GetDistanceMatrix(IList <string> origin, IList <string> dest)
        {
            DistanceResults matrix = new DistanceResults()
            {
                Status = "OK"
            };

            matrix.Origin.AddRange(origin);
            matrix.Destination.AddRange(dest);
            matrix.OriginResponse      = new string[origin.Count];
            matrix.DestinationResponse = new string[dest.Count];

            //find out if an existing resultset was requested and cached
            var filename = getNTFSName(matrix.ToFilename());

            //hash the filename to comply with NTFS rules
            if (File.Exists(filename))
            {
                try
                {
                    return(_service.ReadXML(filename));
                    //return GoogleDistanceMatrix.ReadXML(filename);
                }
                catch { File.Delete(filename); }
            }

            //resultset not cached
            matrix.Row.AddRange(origin.Select(s => new List <DistElement>(dest.Select <string, DistElement>(s2 => null))));

            //so assume some are cached...some are not...
            //how do we group the stuff not cached which gets send remotely

            //get the stuff cached, and assign scores to each origin with a correlated misses
            //getting the misses is easy... how do we group the misses together, so we make a one big request with stuff we want.
            //previously, we did it with same counts...
            // we can do it with flags ANDed together
            //   A B C D E F
            // Z 1 1 1 0 0 1 = 4 (previous code grouped all the 4's together and tried to figure which dest matched)
            // Y 1 1 1 0 0 1 = 4
            // X 1 1 0 1 0 1 = 4
            // W 1 1 0 0 1 1 = 4
            // ----------------
            // 1 1 0 0 0 1 = 3 But if we AND all the bits, then look at it, we know we only request [Z Y X W] x [A B F]
            //                 But this means we have to do a cross-AND with every row, of every origin with same number of misses, to determine 2nd-order sort
            //                   plus the excluded [Z Y X W] x [C D E], which only has 4x 1's, out of 12 requests
            //
            // so this turns above into
            //   A B F          C D E
            // Z 1 1 1 = 3      1 0 0
            // Y 1 1 1 = 3      1 0 0
            // X 1 1 1 = 3      0 1 0
            // W 1 1 1 = 3      0 0 1
            //
            //                  C
            // Z                1
            // Y                1
            //                    D
            // X                  1
            //                      E
            // W                    1
            //
            // so to minimize the number of costable requests to google..., though we can call [Z Y X W] x [C D E], with a 75% miss rate
            // is a
            var notfound  = false;
            var matrixRow = matrix.Row;
            var oricount  = origin.Count;
            var destcount = dest.Count;

            bool[,] found = new bool[destcount, oricount];
            for (int j = 0; j < oricount; j++)
            {
                for (int i = 0; i < destcount; i++)
                {
                    var from = origin[j];
                    var to   = dest[i];
                    if (this.Contains(from, to))
                    {
                        found[j, i]     = true;
                        matrixRow[j][i] = this[from, to];
                    }
                    else if (!notfound)
                    {
                        notfound = true;
                    }
                }
            }

            if (notfound)
            {
                ReduceSurfaceAreaByReordering waterdrop = new ReduceSurfaceAreaByReordering()
                {
                    Area = found
                };
                waterdrop.SortPass += delegate(object sender, EventArgs e)
                {
                    Console.WriteLine("Finding missing elements");
                };
                BiggestContiguousBlock skimmer = new BiggestContiguousBlock(waterdrop);
                foreach (var item in skimmer.Chunkify(false))
                {
                    //item.SetAllTrue();

                    const int GOOGLE_MAX_ORIGIN_OR_DEST          = 25;
                    const int GOOGLE_MAX_REQUESTS                = 100;
                    const int GOOGLE_DEFACTO_MAX_ORIGIN_AND_DEST = 10;
                    foreach (var subitem in item.Chunkify(GOOGLE_DEFACTO_MAX_ORIGIN_AND_DEST))
                    {
                        var suborigin = subitem.RowIndices.Select(s => origin[s]).ToArray();
                        var subdest   = subitem.ColIndices.Select(s => dest[s]).ToArray();

                        if (this.RemoteRetreive != null)
                        {
                            this.RemoteRetreive(this, new UserRequestEventArgs("[" + string.Join("; ", suborigin) + "] to [" + string.Join("; ", subdest) + "]"));
                        }

                        var googleresults = _service.RetryDistancesFromGoogle(10, 1000, suborigin, subdest);  //GetDistancesFromGoogle(suborigin, subdest); //change this to
                        var row           = 0;
                        var col           = 0;
                        foreach (var fromindex in subitem.RowIndices)
                        {
                            col = 0;
                            foreach (var toindex in subitem.ColIndices)
                            {
                                var from = origin[fromindex];
                                var to   = dest[toindex];
                                var data = googleresults.Row[row][col++];
                                if (data.Status == "OK")
                                {
                                    matrixRow[fromindex][toindex] = data;
                                }
                                else
                                if (this.UserRequestError != null)
                                {
                                    this.UserRequestError(this, new UserRequestEventArgs("[" + from + "] to [" + to + "]", data.Status));
                                }
                            }
                            row++;
                        }

                        //until alias system is up, update with user requested name
                        row = 0;
                        col = 0;
                        foreach (var fromindex in subitem.RowIndices)
                        {
                            matrix.OriginResponse[fromindex] = googleresults.Origin[row++];
                        }
                        foreach (var toindex in subitem.ColIndices)
                        {
                            matrix.DestinationResponse[toindex] = googleresults.Destination[col++];
                        }

                        subitem.SetAllTrue();
                    }
                }



                _service.WriteXML(matrix, filename);
            }


            //and group the stuff cached, in such a way that it takes the best use of the deserialized object cache... (obviously file caching works better too)

            return(matrix);
        }
        public DistanceResults GetDistancesFromGoogle(string[] from, string[] to)
        {
            string joined1 = string.Join("|", ToUrlSafeRequest(from));
            string joined2 = from == to ? joined1 : string.Join("|", ToUrlSafeRequest(to));
            // string url = "http://maps.googleapis.com/maps/api/distancematrix/xml?origins=New+York+NY|Seattle&destinations=San+Francisco|New+York+NY|Boston&mode=driving&language=en-US&sensor=false";
            string url = string.Format(@"https://maps.googleapis.com/maps/api/distancematrix/xml?key={0}&origins={1}&destinations={2}&mode=driving&language=en-US&sensor=false", __API_KEY, joined1, joined2);

            XmlDocument doc = MakeRequest(url);

            /*
             * <?xml version="1.0" encoding="UTF-8"?>
             * <DistanceMatrixResponse>
             *  <status>OK</status>
             *  <origin_address>New York, NY, USA</origin_address>
             *  <origin_address>Seattle, WA, USA</origin_address>
             *  <destination_address>San Francisco, CA, USA</destination_address>
             *  <destination_address>New York, NY, USA</destination_address>
             *  <destination_address>Boston, MA, USA</destination_address>
             *  <row>
             *      <element>
             *          <status>OK</status>
             *          <duration>
             *          <value>152102</value>
             *          <text>1 day 18 hours</text>
             *          </duration>
             *          <distance>
             *          <value>4674274</value>
             *          <text>4,674 km</text>
             *          </distance>
             *      </element>
             *      <element>
             *          <status>OK</status>
             *          <duration>
             *          <value>0</value>
             *          <text>1 min</text>
             *          </duration>
             *          <distance>
             *          <value>0</value>
             *          <text>1 m</text>
             *          </distance>
             *      </element>
             *      <element>
             *          <status>OK</status>
             *          <duration>
             *          <value>13039</value>
             *          <text>3 hours 37 mins</text>
             *          </duration>
             *          <distance>
             *          <value>346503</value>
             *          <text>347 km</text>
             *          </distance>
             *      </element>
             *  </row>
             *  <row>
             *      <element>
             *          <status>OK</status>
             *          <duration>
             *          <value>44447</value>
             *          <text>12 hours 21 mins</text>
             *          </duration>
             *          <distance>
             *          <value>1299975</value>
             *          <text>1,300 km</text>
             *          </distance>
             *      </element>
             *      <element>
             *          <status>OK</status>
             *          <duration>
             *          <value>148892</value>
             *          <text>1 day 17 hours</text>
             *          </duration>
             *          <distance>
             *          <value>4589407</value>
             *          <text>4,589 km</text>
             *          </distance>
             *      </element>
             *      <element>
             *          <status>OK</status>
             *          <duration>
             *          <value>158468</value>
             *          <text>1 day 20 hours</text>
             *          </duration>
             *          <distance>
             *          <value>4901431</value>
             *          <text>4,901 km</text>
             *          </distance>
             *      </element>
             *  </row>
             * </DistanceMatrixResponse>
             */

            int             value      = 0;
            DistanceResults result     = new DistanceResults();
            XmlNode         statusnode = doc.DocumentElement.SelectSingleNode("/DistanceMatrixResponse/status");

            if (statusnode != null)
            {
                result.Status = statusnode.InnerText;
            }

            XmlNodeList orinode = doc.DocumentElement.SelectNodes("/DistanceMatrixResponse/origin_address");

            foreach (XmlNode node in orinode)
            {
                result.Origin.Add(node.InnerText);
            }

            XmlNodeList destnode = doc.DocumentElement.SelectNodes("/DistanceMatrixResponse/destination_address");

            foreach (XmlNode node in destnode)
            {
                result.Destination.Add(node.InnerText);
            }


            XmlNodeList rownode = doc.DocumentElement.SelectNodes("/DistanceMatrixResponse/row");

            foreach (XmlNode node in rownode)
            {
                List <DistElement> row     = new List <DistElement>();
                XmlNodeList        colnode = node.SelectNodes("element");
                foreach (XmlNode elenode in colnode)
                {
                    DistElement element   = new DistElement();
                    XmlNode     statmnode = elenode.SelectSingleNode("status");
                    if (statmnode != null)
                    {
                        element.Status = statmnode.InnerText;
                    }

                    XmlNode dursnode = elenode.SelectSingleNode("duration/value");
                    if (dursnode != null && int.TryParse(dursnode.InnerText, out value))
                    {
                        element.DurationSeconds = dursnode.InnerText;
                    }
                    XmlNode durnode = elenode.SelectSingleNode("duration/text");
                    if (durnode != null)
                    {
                        element.DurationText = durnode.InnerText;
                    }

                    XmlNode dstmnode = elenode.SelectSingleNode("distance/value");
                    if (dstmnode != null && int.TryParse(dstmnode.InnerText, out value))
                    {
                        element.DistanceMeters = dstmnode.InnerText;
                    }
                    XmlNode dstnode = elenode.SelectSingleNode("distance/text");
                    if (dstnode != null)
                    {
                        element.DistanceText = dstnode.InnerText;
                    }

                    row.Add(element);
                }

                result.Row.Add(row);
            }

            return(result);
        }
        static int Main(string[] args)
        {
            bool __USER_PERMISSION_FOR_GOOGLE = false;

            if (args.Length == 0 && System.Console.WindowWidth != 0 && System.Console.WindowHeight != 0)
            {
                showUsage();
                return(1);
            }

            if (string.IsNullOrWhiteSpace(GoogleApiKeys.DistanceApiKey))
            {
                Console.WriteLine("No API key for Distance API.  Please enter:");
                Console.Write(">");
                var key = Console.ReadLine();
                if (string.IsNullOrWhiteSpace(key))
                {
                    Console.WriteLine("Bad API key");
                    return(2);
                }
                else
                {
                    GoogleApiKeys.DistanceApiKey = key;
                }
            }

            GoogleLocalDistanceIndex.Index.RemoteRetreive += delegate(object sender, GoogleLocalDistanceIndex.UserRequestEventArgs e)
            {
                Console.WriteLine("Data not local, Remotely requesting : {0}", e.Requested);
                if (!__USER_PERMISSION_FOR_GOOGLE)
                {
                    Console.WriteLine();
                    Console.WriteLine("Google charges $ for Distance Matrix API per Data Element. \nThis application can request hundreds of data elements.  \nPlease check the terms of your Google use agreement about cost.  This may incur billing to the API KEY.");
                    Console.Write("Do you wisth to proceed? (Y/N) >");
                    var response = Console.ReadLine();
                    if (response == "Y" || response == "y")
                    {
                        __USER_PERMISSION_FOR_GOOGLE = true;
                    }
                    else
                    {
                        Environment.Exit(0);
                    }
                    Console.WriteLine();
                }
            };
            GoogleLocalDistanceIndex.Index.UserRequestError += delegate(object sender, GoogleLocalDistanceIndex.UserRequestEventArgs e)
            {
                Console.WriteLine();
                Console.WriteLine("Error with : {0}", e.Requested);
                Console.WriteLine("Message    : {0}", e.Error);
                Console.WriteLine();
            };

            //see if its a file
            int rcode = 0;

            if (args.Length == 1 && System.Console.WindowWidth != 0 && System.Console.WindowHeight != 0 && File.Exists(args[0]))
            {
                using (var fs = File.OpenText(args[0]))
                {
                    var line = fs.ReadToEnd().Split('\n');
                    //DistanceResults location = getCachedDistances(line);
                    DistanceResults location = GoogleLocalDistanceIndex.Index.GetDistanceMatrix(line);
                    if (location != null)
                    {
                        showResult(line, location);
                    }
                    else
                    {
                        rcode = 2;
                    }
                }

                return(rcode);
            }

            // stdin

            /*
             * string sin;
             * if (System.Console.WindowWidth == 0 && System.Console.WindowHeight == 0)
             *  while ((sin = Console.ReadLine()) != null)
             *  {
             *      LocationResults location = getCachedLocation(sin);
             *      if (location != null)
             *          showResult(sin, location);
             *      else
             *          rcode = 2;
             *  }
             */

            // all the arguments must be locations then
            {
                //DistanceResults location = getCachedDistances(args);
                DistanceResults location = GoogleLocalDistanceIndex.Index.GetDistanceMatrix(args);
                if (location != null)
                {
                    showResult(args, location);
                }
                else
                {
                    rcode = 2;
                }
            }

#if DEBUG
            Console.WriteLine("Press [enter] to end");
            Console.ReadLine();
#endif

            return(rcode);
        }