using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
using System.Windows.Input;
using Ookii.Dialogs.Wpf;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using CtrEditor.ObjetosSim;
using CtrEditor.Siemens;
using System.IO;
using Newtonsoft.Json;
using System.Windows;
using CtrEditor.Simulacion;
using System.Diagnostics;
using System.Reflection;
using CommunityToolkit.Mvvm.ComponentModel;
using Xceed.Wpf.Toolkit.PropertyGrid;
using System.Text.RegularExpressions;
using System.Windows.Data;


namespace CtrEditor
{

    public partial class MainViewModel : ObservableObject
    {
        public Stopwatch stopwatch_Sim;

        private double stopwatch_SimPLC_last;
        private double stopwatch_SimModel_last;

        private float TiempoDesdeStartSimulacion;
        private bool Debug_SimulacionCreado = false;

        public SimulationManagerFP simulationManager = new SimulationManagerFP();

        private readonly DispatcherTimer _timerSimulacion;

        public Canvas MainCanvas;

        [ObservableProperty]
        private DatosDeTrabajo datosDeTrabajo;

        [ObservableProperty]
        private ObservableCollection<string> listaImagenes;  // Publicación de las claves del diccionario        

        [ObservableProperty]
        public ObservableCollection<TipoSimulable> listaOsBase;

        public ICommand StartSimulationCommand { get; }
        public ICommand StopSimulationCommand { get; }
        public ICommand ItemDoubleClickCommand { get; private set; }

        public ICommand TBStartSimulationCommand { get; }
        public ICommand TBStopSimulationCommand { get; }
        public ICommand TBSaveCommand { get; }
        public ICommand TBConnectPLCCommand { get; }
        public ICommand TBDisconnectPLCCommand { get; }

        public ICommand TBEliminarUserControlCommand { get; }
        public ICommand TBDuplicarUserControlCommand { get; }

        public ICommand OpenWorkDirectoryCommand { get; }

        // Evento que se dispara cuando se selecciona una nueva imagen
        public event EventHandler<string> ImageSelected;
        public event EventHandler<TickSimulacionEventArgs> TickSimulacion;

        // Propiedades

        private bool habilitarEliminarUserControl;

        private MainWindow mainWindow;

        public MainWindow MainWindow { get => mainWindow; set => mainWindow = value; }


        [ObservableProperty]
        private float canvasLeft;

        [ObservableProperty]
        private float canvasTop;

        [ObservableProperty]
        private bool isSimulationRunning;

        partial void OnIsSimulationRunningChanged(bool value)
        {
            CommandManager.InvalidateRequerySuggested(); // Notificar que el estado de los comandos ha cambiado
        }

        [ObservableProperty]
        private bool isConnected;

        partial void OnIsConnectedChanged(bool value)
        {
            CommandManager.InvalidateRequerySuggested();
        }

        public string directorioTrabajo
        {
            get => EstadoPersistente.Instance.directorio;
            set
            {
                if (value != null)
                {
                    EstadoPersistente.Instance.directorio = value; // Actualizar el estado persistente
                    EstadoPersistente.Instance.GuardarEstado();  // Guardar el estado actualizado
                    DatosDeTrabajo.CargarImagenes();
                    ListaImagenes = new ObservableCollection<string>(DatosDeTrabajo.Imagenes.Keys);  // Actualizar claves
                    SelectedImage = null;
                    if (EstadoPersistente.Instance.imagen != null && EstadoPersistente.Instance.imagen.Length>0)
                        SelectedImage = EstadoPersistente.Instance.imagen;
                    else if (ListaImagenes.FirstOrDefault() != null)
                        SelectedImage = ListaImagenes.FirstOrDefault();

                    OnPropertyChanged(nameof(directorioTrabajo));  // Notificar el cambio de propiedad
                    OnPropertyChanged(nameof(ListaImagenes)); // Notificar que la lista de imágenes ha cambiado
                }
            }
        }

        [ObservableProperty]
        private PLCViewModel pLCViewModel;

        [ObservableProperty]
        private string selectedImage;

        partial void OnSelectedImageChanged(string value)
        {
            if (value != null)
            {
                StopSimulation();
                // SaveStateObjetosSimulables();  // Guarda el estado antes de cambiar la imagen
                ImageSelected?.Invoke(this, datosDeTrabajo.Imagenes[value]); // Dispara el evento con la nueva ruta de imagen
                LoadStateObjetosSimulables();
                EstadoPersistente.Instance.imagen = value;
                EstadoPersistente.Instance.GuardarEstado();
            }
        }

        [ObservableProperty]
        private osBase selectedItemOsList;

        partial void OnSelectedItemOsListChanged(osBase value)
        {
            if (value != null)
                habilitarEliminarUserControl = true;
            else
                habilitarEliminarUserControl = false;
        }

        [ObservableProperty]
        private TipoSimulable selectedItem;

        public ICollectionView ObjetosSimulablesFiltered { get; }
        public ICollectionView ObjetosSimulablesAllPages { get; }

        [ObservableProperty]
        public ObservableCollection<osBase> objetosSimulables;


        //
        // Constructor
        //

        public MainViewModel()
        {
            OpenWorkDirectoryCommand = new RelayCommand(OpenWorkDirectory);
            datosDeTrabajo = new DatosDeTrabajo();

            ObjetosSimulables = new ObservableCollection<osBase>();

            ListaOsBase = new ObservableCollection<TipoSimulable>();

            // Inicializa el PLCViewModel
            PLCViewModel = new PLCViewModel();
            PLCViewModel.RefreshEvent += OnRefreshEvent;

            InitializeTipoSimulableList();

            ItemDoubleClickCommand = new ParameterizedRelayCommand(ExecuteDoubleClick);

            _timerSimulacion = new DispatcherTimer();
            _timerSimulacion.Interval = TimeSpan.FromMilliseconds(1); // ajusta el intervalo según sea necesario
            _timerSimulacion.Tick += OnTickSimulacion;

            StartSimulationCommand = new RelayCommand(StartSimulation);
            StopSimulationCommand = new RelayCommand(StopSimulation);

            TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
            TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);
            TBSaveCommand = new RelayCommand(Save);
            TBConnectPLCCommand = new RelayCommand(ConnectPLC, () => !IsConnected);
            TBDisconnectPLCCommand = new RelayCommand(DisconnectPLC, () => IsConnected);

            TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
            TBDuplicarUserControlCommand = new RelayCommand(DuplicarUserControl, () => habilitarEliminarUserControl);

            stopwatch_Sim = new Stopwatch();
            stopwatch_Sim.Start();
        }

        public void LoadInitialData()
        {
            // Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected"
            directorioTrabajo = EstadoPersistente.Instance.directorio;
        }

        // Crear un nuevo Objeto
        private void ExecuteDoubleClick(object parameter)
        {
            if (parameter is TipoSimulable tipoSimulable)
            {
                CrearObjetoSimulableEnCentroCanvas(tipoSimulable.Tipo);
            }
        }

        public void CrearObjetoSimulableEnCentroCanvas(Type tipoSimulable)
        {
            var CentroCanvas = MainWindow.ObtenerCentroCanvasMeters();
            CrearObjetoSimulable(tipoSimulable, CentroCanvas.X, CentroCanvas.Y);
        }

        public osBase CrearObjetoSimulable(Type tipoSimulable, float Left, float Top)
        {
            // Crear una nueva instancia del osBase correspondiente
            osBase? NuevoOsBase = UserControlFactory.GetInstanceForType(tipoSimulable);

            NuevoOsBase.Left = Left;
            NuevoOsBase.Top = Top;

            if (NuevoOsBase != null)
            {
                if (CrearUserControlDesdeObjetoSimulable(NuevoOsBase))
                    // Añadir el nuevo osBase a la colección de objetos simulables
                    ObjetosSimulables.Add(NuevoOsBase);
            }
            return NuevoOsBase;
        }

        // Crear UserControl desde osBase : Nuevo o desde Deserealizacion
        private bool CrearUserControlDesdeObjetoSimulable(osBase osObjeto)
        {
            Type tipoObjeto = osObjeto.GetType();

            // Obtén el UserControl correspondiente para el tipo de objeto
            UserControl? userControl = UserControlFactory.GetControlForType(tipoObjeto);

            if (userControl != null)
            {
                // Asignar los datos al UserControl
                UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager);
                osObjeto._mainViewModel = this;

                MainWindow.AgregarRegistrarUserControlCanvas(userControl);

                return true;
            }
            return false;
        }

        public void RemoverObjetoSimulable(osBase osObjeto)
        {
            if (osObjeto != null && ObjetosSimulables.Contains(osObjeto))
            {
                ObjetosSimulables.Remove(osObjeto);
                if (osObjeto.VisualRepresentation != null)
                    MainWindow.EliminarUserControlDelCanvas(osObjeto.VisualRepresentation);
            }
        }

        private void DuplicarUserControl()
        {
            if (SelectedItemOsList is osBase objDuplicar)
            {
                StopSimulation();
                DisconnectPLC();

                objDuplicar.SalvarDatosNoSerializables();

                var settings = new JsonSerializerSettings
                {
                    Formatting = Formatting.Indented,
                    NullValueHandling = NullValueHandling.Ignore,
                    TypeNameHandling = TypeNameHandling.All
                };

                try
                {
                    // Serializar
                    var serializedData = JsonConvert.SerializeObject(objDuplicar, settings);
                    // Duplicar
                    var NuevoObjetoDuplicado = JsonConvert.DeserializeObject<osBase>(serializedData, settings);
                    if (NuevoObjetoDuplicado != null)
                    {
                        string nombre = NuevoObjetoDuplicado.Nombre;

                        // Expresión regular para identificar un nombre que termina con _número
                        Regex regex = new Regex(@"_(\d+)$");

                        if (regex.IsMatch(nombre))
                        {
                            // Extraer el número actual y sumarle 1
                            var match = regex.Match(nombre);
                            int numeroActual = int.Parse(match.Groups[1].Value);
                            int nuevoNumero = numeroActual + 1;

                            // Reemplazar el número en el nombre
                            nombre = regex.Replace(nombre, $"_{nuevoNumero}");
                        }
                        else
                        {
                            // Si no termina con _número, añadir _1
                            nombre += "_1";
                        }

                        NuevoObjetoDuplicado.Nombre = nombre;
                        NuevoObjetoDuplicado.Left += 0.5f;
                        NuevoObjetoDuplicado.Top += 0.5f;
                        ObjetosSimulables.Add(NuevoObjetoDuplicado);
                        CrearUserControlDesdeObjetoSimulable(NuevoObjetoDuplicado);
                    }
                }
                catch
                {
                    // Log error or handle it accordingly
                }
                finally
                {
                    objDuplicar.RestaurarDatosNoSerializables();
                }
            }
        }

        private void EliminarUserControl()
        {
            if (SelectedItemOsList is osBase objEliminar)
            {
                RemoverObjetoSimulable(objEliminar);

            }
        }

        private void InitializeTipoSimulableList()
        {
            var baseType = typeof(osBase);
            var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => type.IsSubclassOf(baseType) && !type.IsAbstract && typeof(IosBase).IsAssignableFrom(type));

            foreach (var type in types)
            {
                var methodInfo = type.GetMethod("NombreClase", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                string nombre = methodInfo != null ? methodInfo.Invoke(null, null)?.ToString() : "Nombre no encontrado";

                ListaOsBase.Add(new TipoSimulable { Nombre = nombre, Tipo = type });
            }
        }


        private void StartSimulation()
        {
            IsSimulationRunning = true;

            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.UpdateGeometryStart();

            simulationManager.Debug_DrawInitialBodies();
            TiempoDesdeStartSimulacion = 0;
            Debug_SimulacionCreado = true;

            _timerSimulacion.Start();
        }

        private void StopSimulation()
        {
            IsSimulationRunning = false;

            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.SimulationStop();

            if (Debug_SimulacionCreado)
            {
                simulationManager.Debug_ClearSimulationShapes();
                Debug_SimulacionCreado = false;
            }
            _timerSimulacion.Stop();
        }

        private void OnTickSimulacion(object sender, EventArgs e)
        {
            // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
            var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last;
            stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds;

            // Eliminar el diseño de Debug luego de 2 segundos
            if (TiempoDesdeStartSimulacion > 12000)
                simulationManager.Debug_ClearSimulationShapes();
            else
                TiempoDesdeStartSimulacion += (float)elapsedMilliseconds;

            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.UpdateGeometryStep();

            simulationManager.Step();

            var objetosSimulablesCopy = new List<osBase>(ObjetosSimulables);

            foreach (var objetoSimulable in objetosSimulablesCopy)
            {
                if (!objetoSimulable.RemoverDesdeSimulacion)
                    objetoSimulable.UpdateControl((int)elapsedMilliseconds);
                else
                    RemoverObjetoSimulable(objetoSimulable);
            }

        }

        private void ConnectPLC()
        {
            PLCViewModel.Connect();
        }

        private void DisconnectPLC()
        {
            PLCViewModel.Disconnect();
            IsConnected = false;
            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.SetPLC(null);

        }

        private void OnRefreshEvent(object sender, EventArgs e)
        {
            if (PLCViewModel.IsConnected)
            {
                if (!isConnected)
                {
                    IsConnected = true;
                    foreach (var objetoSimulable in ObjetosSimulables)
                        objetoSimulable.SetPLC(PLCViewModel.PLCInterface);
                }
                // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
                var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimPLC_last;
                stopwatch_SimPLC_last = stopwatch_Sim.Elapsed.TotalMilliseconds;


                // Reiniciar el cronómetro para la próxima medición
                foreach (var objetoSimulable in ObjetosSimulables)
                    objetoSimulable.UpdatePLC(PLCViewModel.PLCInterface, (int)elapsedMilliseconds);
            }
        }

        private void OpenWorkDirectory()
        {
            var dialog = new VistaFolderBrowserDialog();
            if (dialog.ShowDialog() == true) // Mostrar el diálogo y comprobar si el resultado es positivo
            {
                directorioTrabajo = dialog.SelectedPath;  // Actualizar la propiedad que también actualiza el estado persistente
            }
        }

        // 
        //  Lista de osBase
        //

        public void Save()
        {
            SaveStateObjetosSimulables();
        }

        public void SaveStateObjetosSimulables()
        {
            if (SelectedImage != null)
            {
                StopSimulation();
                DisconnectPLC();

                ObservableCollection<osBase> _objetosSimulables = new ObservableCollection<osBase>();
                ObservableCollection<osBase> _objetosSimulablesAllPages = new ObservableCollection<osBase>();

                foreach (var obj in ObjetosSimulables)
                {
                    // Guardar referencias temporales
                    obj.SalvarDatosNoSerializables();
                    if (!obj.Enable_on_all_pages)
                        _objetosSimulables.Add(obj);
                    else
                        _objetosSimulablesAllPages.Add(obj);
                }

                // Salvar los objetos de la pagina actual

                // Crear un objeto que incluya tanto los ObjetosSimulables como el UnitConverter y PLC_ConnectionData
                var dataToSerialize = new SimulationData
                {
                    ObjetosSimulables = _objetosSimulables,
                    UnitConverter = PixelToMeter.Instance.calc,
                    PLC_ConnectionData = PLCViewModel
                };

                // Ruta del archivo a ser guardado
                var path = DatosDeTrabajo.ObtenerPathImagenConExtension(SelectedImage, ".json");
                if (path != null)
                    SerializarYSalvar(dataToSerialize, path);

                // Salvar los objetos de todas las paginas

                // Ruta del archivo a ser guardado
                path = DatosDeTrabajo.ObtenerPathAllPages(".json");
                if (path != null)
                    SerializarYSalvar(_objetosSimulablesAllPages, path);

                // Restaurar las propiedades originales de los objetos
                foreach (var obj in ObjetosSimulables)
                    obj.RestaurarDatosNoSerializables();
            }
        }

        private void SerializarYSalvar(object listaObjetos, string path)
        {

            // Verificar si el archivo ya existe y crear un respaldo
            if (File.Exists(path))
            {
                var backupPath = Path.ChangeExtension(path, ".bak");
                File.Copy(path, backupPath, true);  // Copia el archivo existente a un nuevo archivo .bak, sobrescribiendo si es necesario
            }


            var settings = new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
               // PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                NullValueHandling = NullValueHandling.Ignore,
                TypeNameHandling = TypeNameHandling.Auto
            };

            // Serializar
            var serializedData = JsonConvert.SerializeObject(listaObjetos, settings);
            File.WriteAllText(path, serializedData);  // Escribir el nuevo archivo JSON
        }

        public void LoadStateObjetosSimulables()
        {
            try
            {
                StopSimulation();
                DisconnectPLC();
                ObjetosSimulables.Clear();
                simulationManager.Clear();
                if (SelectedImage != null)
                {
                    var settings = new JsonSerializerSettings
                    {
                        TypeNameHandling = TypeNameHandling.Auto,
                        ObjectCreationHandling = ObjectCreationHandling.Replace,
                        // PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                        ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
                    };

                    string jsonPath = datosDeTrabajo.ObtenerPathImagenConExtension(SelectedImage, ".json");
                    if (File.Exists(jsonPath))
                    {
                        string jsonString = File.ReadAllText(jsonPath);


                        var simulationData = JsonConvert.DeserializeObject<SimulationData>(jsonString, settings);
                        if (simulationData != null)
                        {
                            if (simulationData.ObjetosSimulables is not null)
                                ObjetosSimulables = simulationData.ObjetosSimulables;

                            if (simulationData.PLC_ConnectionData is not null)
                                PLCViewModel = simulationData.PLC_ConnectionData;
                            else
                                PLCViewModel = new PLCViewModel();

                            PixelToMeter.Instance.calc = simulationData.UnitConverter;

                            // Re-register to the events
                            PLCViewModel.RefreshEvent += OnRefreshEvent;

                        }
                    }

                    jsonPath = DatosDeTrabajo.ObtenerPathAllPages(".json");
                    if (File.Exists(jsonPath))
                    {
                        string jsonString = File.ReadAllText(jsonPath);

                        ObservableCollection<osBase> _objetosSimulablesAllPages = new ObservableCollection<osBase>();

                        _objetosSimulablesAllPages = JsonConvert.DeserializeObject<ObservableCollection<osBase>>(jsonString, settings);
                        if (_objetosSimulablesAllPages != null)
                            foreach (var obj in _objetosSimulablesAllPages)
                                ObjetosSimulables.Add(obj);
                    }
                    // Recorrer la colección de objetos simulables
                    foreach (var objetoSimulable in ObjetosSimulables)
                        if (objetoSimulable != null) CrearUserControlDesdeObjetoSimulable(objetoSimulable);
                }
            }
            catch { /* Consider logging the error or handling it appropriately */ }
        }

        // Se cargan los datos de cada UserControl en el StackPanel
        public void CargarPropiedadesosDatos(osBase selectedObject, PropertyGrid PanelEdicion, ResourceDictionary Resources)
        {
            UserControlFactory.CargarPropiedadesosDatos(selectedObject, PanelEdicion);
        }

        private RelayCommand saveCommand;
        public ICommand SaveCommand => saveCommand ??= new RelayCommand(Save);

        private void Save(object commandParameter)
        {
        }

        private RelayCommand exitCommand;
        public ICommand ExitCommand => exitCommand ??= new RelayCommand(Exit);

        private void Exit()
        {
            Save();
            Application.Current.Shutdown();
        }
    }
    public class SimulationData
    {
        public ObservableCollection<osBase>? ObjetosSimulables { get; set; }
        public UnitConverter? UnitConverter { get; set; }
        public PLCViewModel? PLC_ConnectionData { get; set; }
    }

    public class TipoSimulable
    {
        public string? Nombre { get; set; }
        public Type? Tipo { get; set; }
    }

    public class TickSimulacionEventArgs : EventArgs
    {
        // Aquí puedes agregar propiedades o campos para pasar información adicional
        // en el evento TickSimulacion
    }

}
