using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using PhoneApp3.Resources;
//using PhoneApp3.ViewModels;
using Microsoft.Phone.Maps.Controls;
using System.Device.Location;
using System.Threading;
//using Microsoft.Phone.Maps.Toolkit;
using Windows.Devices.Geolocation;

using System.IO;
using System.Threading.Tasks;
using Windows.Storage;
using System.Windows.Threading;
using System.Diagnostics;
using Microsoft.Phone.Maps.Services;

namespace PhoneApp3
{
    public partial class MainPage : PhoneApplicationPage
    {
        private Geolocator geolocator;
        DispatcherTimer Timer;
        // Constructeur
        public MainPage()
        {
            

            InitializeComponent();


            Timer = new DispatcherTimer();
            Timer.Tick += TimerOnTick;
            Timer.Interval = new TimeSpan(1, 0, 0);
            Timer.Start();

        }


        private async void TimerOnTick(object sender, object o)
        {
            Geolocator geolocator = new Geolocator();
            geolocator.DesiredAccuracyInMeters = 50;

            try
            {
                Geoposition geoposition = await geolocator.GetGeopositionAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(10));

                Dispatcher.BeginInvoke(async () =>
                {
                    await WriteToFile(geoposition.Coordinate.Point.Position.Latitude, geoposition.Coordinate.Point.Position.Longitude, DateTime.Now);


                });
            }
            catch (UnauthorizedAccessException)
            {
                MessageBox.Show("Le service de location est désactivé dans les paramètres du téléphone");
            }
            catch (Exception ex)
            {
            }
        }


        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            geolocator = new Geolocator { DesiredAccuracy = PositionAccuracy.High, MovementThreshold = 20 };



            geolocator.StatusChanged += geolocator_StatusChanged;
            geolocator.PositionChanged += geolocator_PositionChanged;

            base.OnNavigatedTo(e);
        }


        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            geolocator.PositionChanged -= geolocator_PositionChanged;
            geolocator.StatusChanged -= geolocator_StatusChanged;
            geolocator = null;

            base.OnNavigatedFrom(e);
        }


        private async Task WriteToFile(double x, double y, DateTime t)
        {

            byte[] fileByteX = System.Text.Encoding.UTF8.GetBytes(x.ToString().ToCharArray());
            byte[] fileByteY = System.Text.Encoding.UTF8.GetBytes(y.ToString().ToCharArray());
            byte[] fileByteT = System.Text.Encoding.UTF8.GetBytes((t.DayOfWeek.ToString() + " " + t.Hour.ToString()).ToCharArray());

            byte[] br = System.Text.Encoding.UTF8.GetBytes("\n");
            byte[] ptVir = System.Text.Encoding.UTF8.GetBytes(";");
            // Get the local folder.
            StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;

            // Create a new file in the local folder if exists named DataFile.txt.
            var file = await local.CreateFileAsync("GPS.txt",
           CreationCollisionOption.OpenIfExists);
            //var file = await local.OpenStreamForReadAsync("GPSTest.txt");

            using (var s = await file.OpenStreamForWriteAsync())
            {
                s.Seek(0, SeekOrigin.End);
                s.Write(br, 0, br.Length);
                s.Write(fileByteX, 0, fileByteX.Length);
                s.Write(ptVir, 0, ptVir.Length);
                s.Write(fileByteY, 0, fileByteY.Length);
                s.Write(ptVir, 0, ptVir.Length);
                s.Write(fileByteT, 0, fileByteT.Length);
            }

        }



        private void geolocator_StatusChanged(Geolocator sender, StatusChangedEventArgs args)
        {
            string status = "";

            switch (args.Status)
            {
                case PositionStatus.Disabled:
                    status = "Le service de localisation est désactivé dans les paramètres";
                    break;
                case PositionStatus.Initializing:
                    status = "En cours d'initialisation";
                    break;
                case PositionStatus.Ready:
                    status = "Service de localisation prêt";
                    break;
                case PositionStatus.NotAvailable:
                case PositionStatus.NotInitialized:
                case PositionStatus.NoData:
                    break;
            }

            Dispatcher.BeginInvoke(() =>
            {
                Statut.Text = status;
            });
        }

        private void geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
        {
            Dispatcher.BeginInvoke(() =>
            {
            });
        }

        private async void cluster_button(object sender, RoutedEventArgs e)
        {
            
            List <List<string>> list = new List<List<string>>();
            await ReadFile(list);
            
            double[][] rawData = new double[list.Count][];
            for (int i = 0; i < list.Count;i++)
            {
                rawData[i] = new double[] { Double.Parse(list[i][0]), Double.Parse(list[i][1]), 3 };
            }
           
            int[] clustering = Cluster(rawData, 3);
            
            await ShowClustered(list, clustering, 3, 1);
            
            for (int i = 0; i < list.Count; i++)
            {
                Debug.WriteLine(list[i][2] + " " + list[i][3]);
            }

        }

        private async Task ReadFile(List<List<string>> list)
        {
            // Get the local folder.
            StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;

            if (local != null)
            {

                // Get the file.
                var file = await local.OpenStreamForReadAsync("GPS.txt");

                // Read the data.
                using (StreamReader streamReader = new StreamReader(file))
                {
                    string s = "";
                    while((s=streamReader.ReadLine()) != null)
                    {
                        
                        
                        if (s == "") continue;
                        string[] data = s.Split(';');
                        list.Add(new List<string> { data[0], data[1], data[2], ""});
                        
                    }
                    
                }
                
            }
        }



        // ============================================================================

        public static int[] Cluster(double[][] rawData, int numClusters)
        {
            // k-means clustering
            // index of return is tuple ID, cell is cluster ID
            // ex: [2 1 0 0 2 2] means tuple 0 is cluster 2, tuple 1 is cluster 1, tuple 2 is cluster 0, tuple 3 is cluster 0, etc.
            // an alternative clustering DS to save space is to use the .NET BitArray class
            double[][] data = Normalized(rawData); // so large values don't dominate

            bool changed = true; // was there a change in at least one cluster assignment?
            bool success = true; // were all means able to be computed? (no zero-count clusters)

            // init clustering[] to get things started
            // an alternative is to initialize means to randomly selected tuples
            // then the processing loop is
            // loop
            //    update clustering
            //    update means
            // end loop
            int[] clustering = InitClustering(data.Length, numClusters, 0); // semi-random initialization
            double[][] means = Allocate(numClusters, data[0].Length); // small convenience

            int maxCount = data.Length * 10; // sanity check
            int ct = 0;
            while (changed == true && success == true && ct < maxCount)
            {
                ++ct; // k-means typically converges very quickly
                success = UpdateMeans(data, clustering, means); // compute new cluster means if possible. no effect if fail
                changed = UpdateClustering(data, clustering, means); // (re)assign tuples to clusters. no effect if fail
            }
            // consider adding means[][] as an out parameter - the final means could be computed
            // the final means are useful in some scenarios (e.g., discretization and RBF centroids)
            // and even though you can compute final means from final clustering, in some cases it
            // makes sense to return the means (at the expense of some method signature uglinesss)
            //
            // another alternative is to return, as an out parameter, some measure of cluster goodness
            // such as the average distance between cluster means, or the average distance between tuples in 
            // a cluster, or a weighted combination of both


            return clustering;
        }

        private static double[][] Normalized(double[][] rawData)
        {
            // normalize raw data by computing (x - mean) / stddev
            // primary alternative is min-max:
            // v' = (v - min) / (max - min)

            // make a copy of input data
            double[][] result = new double[rawData.Length][];

            for (int i = 0; i < rawData.Length; ++i)
            {

                result[i] = new double[rawData[i].Length - 1];
                Array.Copy(rawData[i], result[i], rawData[i].Length - 1);

            }

            for (int j = 0; j < result[0].Length; ++j) // each col
            {
                double colSum = 0.0;
                for (int i = 0; i < result.Length; ++i)
                    colSum += result[i][j];
                double mean = colSum / result.Length;
                double sum = 0.0;
                for (int i = 0; i < result.Length; ++i)
                    sum += (result[i][j] - mean) * (result[i][j] - mean);
                double sd = sum / result.Length;
                for (int i = 0; i < result.Length; ++i)
                    result[i][j] = (result[i][j] - mean) / sd;
            }
            return result;
        }

        private static int[] InitClustering(int numTuples, int numClusters, int randomSeed)
        {
            // init clustering semi-randomly (at least one tuple in each cluster)
            // consider alternatives, especially k-means++ initialization,
            // or instead of randomly assigning each tuple to a cluster, pick
            // numClusters of the tuples as initial centroids/means then use
            // those means to assign each tuple to an initial cluster.
            Random random = new Random(randomSeed);
            int[] clustering = new int[numTuples];
            for (int i = 0; i < numClusters; ++i) // make sure each cluster has at least one tuple
                clustering[i] = i;
            for (int i = numClusters; i < clustering.Length; ++i)
                clustering[i] = random.Next(0, numClusters); // other assignments random
            return clustering;
        }

        private static double[][] Allocate(int numClusters, int numColumns)
        {
            // convenience matrix allocator for Cluster()
            double[][] result = new double[numClusters][];
            for (int k = 0; k < numClusters; ++k)
                result[k] = new double[numColumns];
            return result;
        }

        private static bool UpdateMeans(double[][] data, int[] clustering, double[][] means)
        {
            // returns false if there is a cluster that has no tuples assigned to it
            // parameter means[][] is really a ref parameter

            // check existing cluster counts
            // can omit this check if InitClustering and UpdateClustering
            // both guarantee at least one tuple in each cluster (usually true)
            int numClusters = means.Length;
            int[] clusterCounts = new int[numClusters];
            for (int i = 0; i < data.Length; ++i)
            {
                int cluster = clustering[i];
                ++clusterCounts[cluster];
            }

            for (int k = 0; k < numClusters; ++k)
                if (clusterCounts[k] == 0)
                    return false; // bad clustering. no change to means[][]

            // update, zero-out means so it can be used as scratch matrix 
            for (int k = 0; k < means.Length; ++k)
                for (int j = 0; j < means[k].Length; ++j)
                    means[k][j] = 0.0;

            for (int i = 0; i < data.Length; ++i)
            {
                int cluster = clustering[i];
                for (int j = 0; j < data[i].Length; ++j)
                    means[cluster][j] += data[i][j]; // accumulate sum
            }

            for (int k = 0; k < means.Length; ++k)
                for (int j = 0; j < means[k].Length; ++j)
                    means[k][j] /= clusterCounts[k]; // danger of div by 0
            return true;
        }

        private static bool UpdateClustering(double[][] data, int[] clustering, double[][] means)
        {
            // (re)assign each tuple to a cluster (closest mean)
            // returns false if no tuple assignments change OR
            // if the reassignment would result in a clustering where
            // one or more clusters have no tuples.

            int numClusters = means.Length;
            bool changed = false;

            int[] newClustering = new int[clustering.Length]; // proposed result
            Array.Copy(clustering, newClustering, clustering.Length);

            double[] distances = new double[numClusters]; // distances from curr tuple to each mean

            for (int i = 0; i < data.Length; ++i) // walk thru each tuple
            {
                for (int k = 0; k < numClusters; ++k)
                    distances[k] = Distance(data[i], means[k]); // compute distances from curr tuple to all k means

                int newClusterID = MinIndex(distances); // find closest mean ID
                if (newClusterID != newClustering[i])
                {
                    changed = true;
                    newClustering[i] = newClusterID; // update
                }
            }

            if (changed == false)
                return false; // no change so bail and don't update clustering[][]

            // check proposed clustering[] cluster counts
            int[] clusterCounts = new int[numClusters];
            for (int i = 0; i < data.Length; ++i)
            {
                int cluster = newClustering[i];
                ++clusterCounts[cluster];
            }

            for (int k = 0; k < numClusters; ++k)
                if (clusterCounts[k] == 0)
                    return false; // bad clustering. no change to clustering[][]

            Array.Copy(newClustering, clustering, newClustering.Length); // update
            return true; // good clustering and at least one change
        }

        private static double Distance(double[] tuple, double[] mean)
        {
            // Euclidean distance between two vectors for UpdateClustering()
            // consider alternatives such as Manhattan distance
            double sumSquaredDiffs = 0.0;
            for (int j = 0; j < tuple.Length; ++j)
                sumSquaredDiffs += Math.Pow((tuple[j] - mean[j]), 2);
            return Math.Sqrt(sumSquaredDiffs);
        }

        private static int MinIndex(double[] distances)
        {
            // index of smallest value in array
            // helper for UpdateClustering()
            int indexOfMin = 0;
            double smallDist = distances[0];
            for (int k = 0; k < distances.Length; ++k)
            {
                if (distances[k] < smallDist)
                {
                    smallDist = distances[k];
                    indexOfMin = k;
                }
            }
            return indexOfMin;
        }

        async Task ShowClustered(List<List<string>> data, int[] clustering, int numClusters, int decimals)
        {
            for (int k = 0; k < numClusters; ++k)
            {
                double means1 = 0;
                double means2 = 0;
                int compteur = 0;
                Console.WriteLine("===================");
                for (int i = 0; i < data.Count; ++i)
                {
                    int clusterID = clustering[i];
                    if (clusterID != k) continue;
                    means1 += Double.Parse(data[i][0]);
                    means2 += Double.Parse(data[i][1]);
                    compteur++;
                }
                means1 /= compteur;
                means2 /= compteur;


                String location = "";
                ReverseGeocodeQuery query = new ReverseGeocodeQuery();
                //query.GeoCoordinate = new GeoCoordinate(geoposition.Coordinate.Point.Position.Latitude, geoposition.Coordinate.Point.Position.Longitude);
                query.GeoCoordinate = new GeoCoordinate(means1, means2);
                
                
                location = await QuerryTaskAsync(query);
                
                for (int i = 0; i < data.Count; ++i)
                {
                    int clusterID = clustering[i];
                    if (clusterID != k) continue;
                    data[i][3] = location;
                }
            }
        } //k
        public Task<string> QuerryTaskAsync(ReverseGeocodeQuery reverseGeocode)
        {
            TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
            EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> queryCompleted = null;
            queryCompleted = (send, arg) =>
            {

                if (arg.Error != null)
                {
                    tcs.SetException(arg.Error);
                }
                else if (arg.Cancelled)
                {
                    tcs.SetCanceled();
                }
                else
                {
                    string location = "";
                    foreach (var address in arg.Result.Select(adrInfo => adrInfo.Information.Address))
                    {
                        location = String.Format("{0} {1} {2} {3} {4} {5} {6} {7} {8} ",
                           address.BuildingFloor, address.BuildingName, address.BuildingRoom, address.BuildingZone, address.District, address.Street, address.City, address.Neighborhood, address.County);
                    }
                    
                    tcs.SetResult(location);
                }
            };

            reverseGeocode.QueryCompleted += queryCompleted;

            reverseGeocode.QueryAsync();

            return tcs.Task;
        }

        private async void getGPS_Click(object sender, RoutedEventArgs e)
        {
            Geolocator geolocator = new Geolocator();
            geolocator.DesiredAccuracyInMeters = 50;

            try
            {
                Geoposition geoposition = await geolocator.GetGeopositionAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(10));

                Dispatcher.BeginInvoke(async () =>
                {
                    await WriteToFile(geoposition.Coordinate.Point.Position.Latitude, geoposition.Coordinate.Point.Position.Longitude, DateTime.Now);


                });
            }
            catch (UnauthorizedAccessException)
            {
                MessageBox.Show("Le service de location est désactivé dans les paramètres du téléphone");
            }
            catch (Exception ex)
            {
            }
        }

        private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
        {
            NavigationService.Navigate(new Uri("/Calendar.xaml", UriKind.Relative));
        }
    }
}