using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services.Trajectory;

abstract class TrajectoryBaseService<TGeo, TCartesian>
    where TGeo : TrajectoryGeoDto
    where TCartesian : TrajectoryCartesianDto, new()
{
    ITrajectoryRepository<TGeo> repository;

    public TrajectoryBaseService(ITrajectoryRepository<TGeo> repository)
    {
        this.repository = repository;
    }

    protected class Location
    {
        public double North { get; set; }
        public double East { get; set; }
        public double Depth { get; set; }
        public TrajectoryGeoDto Trajectory { get; set; } = null!;
    }

    public async Task<IEnumerable<TCartesian>?> GetAsync(int idWell, CancellationToken token)
    {
        var geoCoords = await repository.GetAsync(idWell, token);
        var locs = GetTrajectoryVisualisation(geoCoords);
        var dtos = locs.Select(l => Convert(l));
        return dtos;
    }

    private IEnumerable<Location> GetTrajectoryVisualisation(IEnumerable<TrajectoryGeoDto> geoCoordinates)
    {
        var geoCoordinatesLength = geoCoordinates.Count();
        if (geoCoordinatesLength < 2)
            return new Location[0];

        var cartesianCoordinates = new Location[geoCoordinatesLength];
        cartesianCoordinates[0] = new();

        var geoCoordinatesArray = geoCoordinates.OrderBy(c => c.WellboreDepth).ToArray();
        for (var i = 1; i < geoCoordinatesLength; i++)
        {
            var coordinates = Calculate(cartesianCoordinates[i - 1],
                geoCoordinatesArray[i - 1],
                geoCoordinatesArray[i]);

            cartesianCoordinates[i] = coordinates;
        }

        return cartesianCoordinates;
    }

    protected Location Calculate(Location prevlocation, TrajectoryGeoDto prev, TrajectoryGeoDto current)
    {
        var intervalGeoParams = prev;
        var deltaWellLength = current.WellboreDepth - intervalGeoParams.WellboreDepth;
        var projectionLengthToXYSurface = deltaWellLength * Math.Sin(intervalGeoParams.ZenithAngle * Math.PI / 180);

        var dDepth = deltaWellLength * Math.Cos(intervalGeoParams.ZenithAngle * Math.PI / 180);
        var dNorth = projectionLengthToXYSurface * Math.Sin(intervalGeoParams.AzimuthGeo * Math.PI / 180);
        var dEast = projectionLengthToXYSurface * Math.Cos(intervalGeoParams.AzimuthGeo * Math.PI / 180);

        return new()
        {
            North = prevlocation.North + dNorth,
            East = prevlocation.East + dEast,
            Depth = prevlocation.Depth + dDepth,
            Trajectory = current,
        };
    }

    protected virtual TCartesian Convert(Location location)
    {
        var result = new TCartesian()
        {
            X = location.East,
            Y = -location.Depth,
            Z = -location.North,
        };

        return result;
    }
}

class TrajectoryPlanService: TrajectoryBaseService<TrajectoryGeoPlanDto, TrajectoryCartesianPlanDto>
{
    public TrajectoryPlanService(ITrajectoryPlanRepository repository)
        :base(repository)
    {}

    protected override TrajectoryCartesianPlanDto Convert(Location location)
    {
        var result =  base.Convert(location);
        if (location.Trajectory is TrajectoryGeoPlanDto trajectoryPlan)
        {
            result.Radius = trajectoryPlan.Radius;
            result.Comment = trajectoryPlan.Comment;
        }
        return result;
    }
}

class TrajectoryFactService : TrajectoryBaseService<TrajectoryGeoFactDto, TrajectoryCartesianFactDto>
{
    public TrajectoryFactService(ITrajectoryFactRepository repository)
        : base(repository)
    { }
}


public class TrajectoryService
{
    private TrajectoryPlanService trajectoryPlanService;
    private TrajectoryFactService trajectoryFactService;

    public TrajectoryService(ITrajectoryPlanRepository plannedRepository, ITrajectoryFactRepository factRepository)
    {
        trajectoryPlanService = new TrajectoryPlanService(plannedRepository);
        trajectoryFactService = new TrajectoryFactService(factRepository);
    }

    /// <summary>
    /// Получение плановой и фактической траектории по скважине
    /// </summary>
    /// <param name="idWell">ключ скважины</param>
    /// <param name="token"></param>
    /// <returns></returns>
    public async Task<PlanFactBase<IEnumerable<TrajectoryCartesianPlanDto>, IEnumerable<TrajectoryCartesianFactDto>>> GetTrajectoryCartesianAsync(int idWell, CancellationToken token)
    {
        var result = new PlanFactBase<IEnumerable<TrajectoryCartesianPlanDto>, IEnumerable<TrajectoryCartesianFactDto>>();

        result.Plan = await trajectoryPlanService.GetAsync(idWell, token);
        result.Fact = await trajectoryFactService.GetAsync(idWell, token);

        return result;
    }
}