using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = System.Random;

public class RedCom
{
    private const int DistanciaSinConexion = int.MaxValue;
    private const int GenAleatoriaMaxIntentosPorNodo = 1000;
    private const float GenAleatoriaDistanciaDeConexion = 2.5f;
    private const float GenAleatoriaMinDistanciaEntreNodos = 1.25f;

    public ICollection<AntenaCom> AntenaComs { get; set; }
    public AntenaCom AntenaComEmisora;
    public AntenaCom AntenaComReceptora;
    public AntenaCom AntenaComEstacion;

    public RedCom()
    {
        AntenaComs = new List<AntenaCom>();
    }

    public void Generar(Random random)
    {
        Camera cam = Camera.main;
        float height = 2f * cam.orthographicSize;
        float width = height * cam.aspect;

        GenerarGrafoAleatorio(random, 90, -width/2 + 1, width/2 - 1, -height/2 + 2, height/2 - 1);
        // GenerarGrafoRectangular(random, 8, 8, width - 1, height - 1);

        CalcularTodasLasRutas();

        AntenaComReceptora = AntenaComs.ElementAt(random.Next(AntenaComs.Count));
        AntenaComEmisora = ElegirAntenaComEmisora(random, 6, 10);
        AntenaComEstacion = ElegirAntenaComEstacion(random, 3, 3);
    }

    public void DestruirAntenaCom(AntenaCom antenaADestruir)
    {
        AntenaComs.Remove(antenaADestruir);
        foreach (var a in AntenaComs) {
            a.Conexiones.Remove(antenaADestruir);
        }

        CalcularTodasLasRutas();
    }

    public void CalcularTodasLasRutas()
    {
        foreach (var antena in AntenaComs)
        {
            CalcularRutas(antena);
        }
    }

    public void CalcularRutas(AntenaCom antenaDestino)
    {
        foreach (var antena in AntenaComs)
        {
            antena.Visitada = false;
            antena.SetRuta(antenaDestino, null);
            antena.SetDistancia(antenaDestino, DistanciaSinConexion);
        }

        List<AntenaCom> antenasPorVisitar = new List<AntenaCom>();
        antenaDestino.SetDistancia(antenaDestino, 0);
        antenaDestino.Visitada = true;

        antenasPorVisitar.Add(antenaDestino);
        while (antenasPorVisitar.Any())
        {
            AntenaCom antenaActual = antenasPorVisitar.First();
            antenasPorVisitar.RemoveAt(0);
            antenaActual.Visitada = true;

            foreach (var conexion in antenaActual.Conexiones)
            {
                if (conexion.Visitada)
                {
                    continue;
                }

                int distanciaConexion = antenaActual.GetDistancia(antenaDestino) + 1;
                if (distanciaConexion < conexion.GetDistancia(antenaDestino))
                {
                    conexion.SetDistancia(antenaDestino, distanciaConexion);
                    conexion.SetRuta(antenaDestino, antenaActual);
                }

                antenasPorVisitar.Add(conexion);
            }
        }
    }

    private void GenerarGrafoAleatorio(Random random, int numeroNodos, float x0, float x1, float y0, float y1)
    {
        int idNodo = 0;
        float anchoMapa = Math.Abs(x1 - x0);
        float altoMapa  = Math.Abs(y1 - y0);
        for (int i = 0; i < numeroNodos; i++)
        {
            AntenaCom nuevaAntenaCom = new AntenaCom(idNodo++, this);
            bool posicionExitosa = false;
            while (!posicionExitosa && i < GenAleatoriaMaxIntentosPorNodo)
            {
                nuevaAntenaCom.X = anchoMapa * (float) random.NextDouble() + x0;
                nuevaAntenaCom.Y = altoMapa  * (float) random.NextDouble() + y0;

                posicionExitosa = true;
                foreach (AntenaCom nodo in AntenaComs)
                {
                    double dX = nuevaAntenaCom.X - nodo.X;
                    double dY = nuevaAntenaCom.Y - nodo.Y;
                    float distancia = Mathf.Sqrt((float)(dX * dX + dY * dY));
                    if (distancia < GenAleatoriaMinDistanciaEntreNodos)
                    {
                        posicionExitosa = false;
                        break;
                    }
                }

                i++;
            }

            if (posicionExitosa)
            {
                AntenaComs.Add(nuevaAntenaCom);
            }
        }

        foreach (AntenaCom n in AntenaComs)
        {
            foreach (AntenaCom m in AntenaComs)
            {
                double dX = n.X - m.X;
                double dY = n.Y - m.Y;
                float distancia = Mathf.Sqrt((float)(dX * dX + dY * dY));
                if (distancia < GenAleatoriaDistanciaDeConexion)
                {
                    n.Conexiones.Add(m);
                    m.Conexiones.Add(n);
                }
            }
        }

        UnirGrupos();
    }

    private void GenerarGrafoRectangular(Random random, int anchoGrafo, int altoGrafo, float anchoMapa, float altoMapa)
    {
        float distanciaHorizontal = anchoMapa / (anchoGrafo - 1);
        float distanciaVertical = altoMapa / (altoGrafo - 1);

        int idNodo = 0;
        for (int y = 0; y < altoGrafo; y++)
        {
            for (int x = 0; x < anchoGrafo; x++)
            {
                AntenaCom nuevoAntenaCom = new AntenaCom(idNodo++, this)
                {
                    X = x * distanciaHorizontal - anchoMapa / 2,
                    Y = -y * distanciaVertical + altoMapa / 2
                };
                AntenaComs.Add(nuevoAntenaCom);
            }
        }

        for (int y = 0; y < altoGrafo; y++)
        {
            for (int x = 0; x < anchoGrafo; x++)
            {
                int index = y * anchoGrafo + x;
                int indexDerecha = y * anchoGrafo + x + 1;
                int indexAbajo = (y + 1) * anchoGrafo + x;

                AntenaCom antenaActual = AntenaComs.ElementAt(index);
                ICollection<AntenaCom> conexiones = antenaActual.Conexiones;

                if (random.Next(4) == 0)
                {
                    continue;
                }

                if (x + 1 < anchoGrafo)
                {
                    conexiones.Add(AntenaComs.ElementAt(indexDerecha));
                    AntenaComs.ElementAt(indexDerecha).Conexiones.Add(antenaActual);
                }


                if (y + 1 < altoGrafo)
                {
                    conexiones.Add(AntenaComs.ElementAt(indexAbajo));
                    AntenaComs.ElementAt(indexAbajo).Conexiones.Add(antenaActual);
                }
            }
        }
    }

    private AntenaCom ElegirAntenaComEmisora(Random random, int distanciaMinima, int distanciaMaxima)
    {
        ICollection<AntenaCom> antenasCandidatas = AntenaComs.Where(a => a.GetDistancia(AntenaComReceptora) >= distanciaMinima && a.GetDistancia(AntenaComReceptora) <= distanciaMaxima).ToList();
        if (antenasCandidatas.Count == 0)
        {
            antenasCandidatas = AntenaComs.Where(a => a.GetDistancia(AntenaComReceptora) > distanciaMinima).ToList();
            if (antenasCandidatas.Count == 0)
            {
                antenasCandidatas = AntenaComs;
            }
        }

        return antenasCandidatas.ElementAt(random.Next(antenasCandidatas.Count));
    }

    private AntenaCom ElegirAntenaComEstacion(Random random, int distanciaMinimaReceptora, int distanciaMinimaEmisora)
    {
        ICollection<AntenaCom> antenasCandidatas = AntenaComs.Where(a => a.GetDistancia(AntenaComReceptora) >= distanciaMinimaReceptora && a.GetDistancia(AntenaComEmisora) >= distanciaMinimaEmisora).ToList();
        if (antenasCandidatas.Count == 0)
        {
            antenasCandidatas = AntenaComs;
        }

        return antenasCandidatas.ElementAt(random.Next(antenasCandidatas.Count));
    }

    private void UnirGrupos()
    {
        int cantidadGrupos = int.MaxValue;
        while (cantidadGrupos > 1) {
            foreach (var antena in AntenaComs) {
                antena.Grupo = -1;
            }

            int numeroDeGrupo = 0;
            foreach (var antena in AntenaComs)
            {
                if (antena.Grupo != -1)
                {
                    continue;
                }

                List<AntenaCom> grupoDeAntenaComs = EncontrarGrupo(antena, numeroDeGrupo);
                List<AntenaCom> antenasFueraDelGrupo = AntenaComs.Where(a => !grupoDeAntenaComs.Contains(a)).ToList();

                float distanciaMinima = float.MaxValue;
                AntenaCom conexionInterna = null;
                AntenaCom conexionExterna = null;
                foreach (var antenaEnGrupo in grupoDeAntenaComs)
                {
                    foreach (var antenaOtroGrupo in antenasFueraDelGrupo)
                    {
                        float dX = antenaOtroGrupo.X - antenaEnGrupo.X;
                        float dY = antenaOtroGrupo.Y - antenaEnGrupo.Y;
                        float d2 = dX * dX + dY * dY;

                        if (d2 < distanciaMinima)
                        {
                            distanciaMinima = d2;
                            conexionInterna = antenaEnGrupo;
                            conexionExterna = antenaOtroGrupo;
                        }
                    }
                }

                if (conexionInterna != null) {
                    conexionInterna.Conexiones.Add(conexionExterna);
                    conexionExterna.Conexiones.Add(conexionInterna);
                    conexionInterna.AntenaComGrande = true;
                    conexionExterna.AntenaComGrande = true;

                    // Agrega la sección conectada al grupo actual
                    EncontrarGrupo(conexionExterna, numeroDeGrupo);
                }

                numeroDeGrupo++;
            }
            cantidadGrupos = numeroDeGrupo;
        }
    }

    private List<AntenaCom> EncontrarGrupo(AntenaCom antenaInicial, int numeroDeGrupo)
    {
        foreach (var antena in AntenaComs)
        {
            antena.Visitada = false;
        }
        List<AntenaCom> antenasPorVisitar = new List<AntenaCom>();
        antenasPorVisitar.Add(antenaInicial);
        List<AntenaCom> antenasEnGrupo = new List<AntenaCom>();

        while(antenasPorVisitar.Any()) {
            AntenaCom antenaActual = antenasPorVisitar.First();
            antenasPorVisitar.RemoveAt(0);
            antenaActual.Visitada = true;
            antenaActual.Grupo = numeroDeGrupo;
            antenasEnGrupo.Add(antenaActual);

            foreach (var conexion in antenaActual.Conexiones) {
                if (conexion.Visitada) {
                    continue;
                }

                antenasPorVisitar.Add(conexion);
            }
        }

        return antenasEnGrupo;
    }
};