using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace uBIMEarthTools.Core
{
   public class IDW<T> where T : Distance<T>
    {
        private List<T> dataPoints;
        private int power;

        public IDW(List<T> dataPoints, int power)
        {
            this.dataPoints = dataPoints;
            this.power = power;
        }

        public double Interpolate(T targetPoint)
        {
            double weightedSum = 0;
            double weightSum = 0;

            foreach (var dataPoint in dataPoints)
            {
                double distance = targetPoint.DistanceTo(dataPoint);
                double weight = 1 / Math.Pow(distance, power);

                weightedSum += weight * dataPoint.Value;
                weightSum += weight;
            }

            return weightedSum / weightSum;
        }

        public double Interpolate2(T targetPoint)
        {
            Dictionary<double,double> map = new Dictionary<double,double>();
            foreach (var dataPoint in dataPoints)
            {
                double distance = targetPoint.DistanceTo(dataPoint);
                double weight = 1 / Math.Pow(distance, power);
                map[dataPoint.Value] = map.ContainsKey(dataPoint.Value) ? map[dataPoint.Value] + weight : weight;
            }

            return map.Aggregate((x,y)=> x.Value > y.Value ? x : y).Key;
        }
    }

    internal class Kriging<T> where T : Distance<T>
    {
        private List<T> dataPoints;
        private double range;
        private double nugget;

        public Kriging(List<T> dataPoints, double range, double nugget)
        {
            this.dataPoints = dataPoints;
            this.range = range;
            this.nugget = nugget;
        }

        public double Interpolate(T targetPoint)
        {
            double[] distances = dataPoints.Select(p => targetPoint.DistanceTo(p)).ToArray();
            //range = distances.Max() - distances.Min();
            // Calculate weights based on the spherical variogram model
            double[] weights = distances.Select(d => GaussianVariogram(d)).ToArray();

            // Calculate the interpolated value
            double interpolatedValue = 0;
            double totalWeight = 0;
            
            for (int i = 0; i < dataPoints.Count; i++)
            {
                interpolatedValue += weights[i] * dataPoints[i].Value;
                totalWeight += weights[i];
            }

            return interpolatedValue / totalWeight;
        }

        private double SphericalVariogram(double h)
        {
            if (h == 0)
            {
                return nugget;
            }
            else if (h <= range)
            {
                return nugget + (1.5 * Math.Pow(h / range, 3) - 2.5 * Math.Pow(h / range, 2) + 1);
            }
            else
            {
                return nugget + 1;
            }
        }

        private double GaussianVariogram(double h)
        {
            if (h == 0)
            {
                return 0;
            }
            else
            {
                return nugget + 1 - Math.Exp(-h * h / (range * range));
            }
        }
    }

    class Point3D : Distance<Point3D>
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }

        public override double DistanceTo(Point3D other)
        {
            double dx = this.X - other.X;
            double dy = this.Y - other.Y;
            double dz = this.Z - other.Z;

            return Math.Sqrt(dx * dx + dy * dy + dz * dz);
        }
    }

    class Point2D : Distance<Point2D>
    {
        public double X { get; set; }
        public double Y { get; set; }

        public override double DistanceTo(Point2D other)
        {
            double dx = this.X - other.X;
            double dy = this.Y - other.Y;

            return Math.Sqrt(dx * dx + dy * dy );
        }
    }

    interface IDistance<T>
    {
        double DistanceTo(T other);
    }

    public abstract class Distance<T> : IDistance<T>
    {
        public abstract double DistanceTo(T other);
        public double Value { get; set; }
    }


}
