
using System.Text.Json.Serialization;
using System.Windows;
using CtrEditor.Siemens;
using CtrEditor.Simulacion;
using System.Windows.Media;
using nkast.Aether.Physics2D.Common;
using Siemens.Simatic.Simulation.Runtime;
using System.Windows.Media.Imaging;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Windows.Media.Animation;
using System.Diagnostics;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.ComponentModel;
using System.Configuration;
using System.Windows.Threading;
using System.IO;
using Tesseract;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Newtonsoft.Json;
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
using System.Collections.ObjectModel;
using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection;

namespace CtrEditor.ObjetosSim
{

    public interface IosBase
    {
        static abstract string NombreClase();
    }

    public interface IDataContainer
    {
        [JsonIgnore]
        osBase? Datos { get; set; }
        void Resize(float width, float height);
        void Move(float Left, float Top);
        void Rotate(float Angle);
        void Highlight(bool State);
        int ZIndex();
    }

    public class DataSaveToSerialize
    {
        private MainViewModel? _mainViewModel;
        private UserControl? VisualRepresentation;
        private SimulationManagerFP? simulationManager;

        public DataSaveToSerialize(MainViewModel a, UserControl b, SimulationManagerFP c)
        {
            _mainViewModel = a;
            VisualRepresentation = b;
            simulationManager = c;
        }

        public void DataRestoreAfterSerialize(out MainViewModel a, out UserControl b, out SimulationManagerFP c)
        {
            a = _mainViewModel;
            b = VisualRepresentation;
            c = simulationManager;
        }
    }

    public abstract partial class osBase : ObservableObject
    {
        public virtual string Nombre { get; set; } = "osBase";

        [JsonIgnore]
        private Storyboard _storyboard;
        [JsonIgnore]
        private System.Threading.Timer timer = null;

        public UniqueId Id { get; set; }

        [ObservableProperty]
        [property: Description("X coordinate.")]
        [property: Category("Layout:")]
        private float left;

        partial void OnLeftChanged(float value)
        {
            CanvasSetLeftinMeter(value);
            LeftChanged(value);
        }

        partial void OnLeftChanging(float oldValue, float newValue)
        {
            LeftChanging(oldValue, newValue);
        }

        public virtual void LeftChanged(float value) { }
        public virtual void LeftChanging(float oldValue, float newValue) { }

        [ObservableProperty]
        [property: Description("Y coordinate.")]
        [property: Category("Layout:")]
        private float top;

        partial void OnTopChanged(float value)
        {
            CanvasSetTopinMeter(value);
            TopChanged(value);
        }
        partial void OnTopChanging(float oldValue, float newValue)
        {
            TopChanging(oldValue, newValue);
        }

        public virtual void TopChanged(float value) { }
        public virtual void TopChanging(float oldValue, float newValue) { }


        public void InicializaNuevoObjeto()
        {
            Id = new UniqueId().ObtenerNuevaID();
        }

        // Group        
        [ObservableProperty]
        [property: Description("This is a link to a faceplate. It works like an anchor.")]
        [property: Category("Group:")]
        private string group_Panel;

        protected void UpdateChildsTop(float offsetY)
        {
            if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null)
            {
                foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
                {
                    if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre)
                    {
                        objetoSimulable.Top += offsetY;
                    }
                }
            }
        }
        protected void UpdateChildsLeft(float offsetX)
        {
            if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null)
            {
                foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
                {
                    if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre)
                    {
                        objetoSimulable.Left += offsetX;
                    }
                }
            }
        }

        private void ShowPreviewWindow(Stream imageStream)
        {
            // Create a new window for preview
            Window previewWindow = new Window
            {
                Title = "Preview Captured Image",
                Width = 500,
                Height = 500,
                Content = new Image
                {
                    Source = BitmapFrame.Create(imageStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad),
                    Stretch = Stretch.Uniform
                }
            };

            previewWindow.ShowDialog();
        }


        public string CaptureImageAreaAndDoOCR(float Left, float Top, float Ancho, float Alto)
        {
            if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
            {
                // Asegurarse de que la imagen origen está disponible
                if (imagenDeFondo.Source is BitmapSource bitmapSource)
                {
                    // Obtener los DPI de la imagen original
                    float originalDpiX = (float)bitmapSource.DpiX;
                    float originalDpiY = (float)bitmapSource.DpiY;

                    // Estándar DPI en el que el Canvas renderiza la imagen
                    float canvasDpiX = 96; // WPF usually renders at 96 DPI
                    float canvasDpiY = 96;

                    // Calcular el ratio de escala entre el Canvas y la imagen original
                    float scaleFactorX = originalDpiX / canvasDpiX;
                    float scaleFactorY = originalDpiY / canvasDpiY;

                    // Ajustar las coordenadas de recorte en función del ratio de escala
                    int x = (int)MeterToPixels(Left * scaleFactorX);
                    int y = (int)MeterToPixels(Top * scaleFactorY);
                    int width = (int)MeterToPixels(Ancho * scaleFactorX);
                    int height = (int)MeterToPixels(Alto * scaleFactorY);

                    // Validar y ajustar el tamaño del recorte para que se mantenga dentro de los límites de la imagen
                    if (x < 0) x = 0;
                    if (y < 0) y = 0;
                    if (x + width > bitmapSource.PixelWidth) width = bitmapSource.PixelWidth - x;
                    if (y + height > bitmapSource.PixelHeight) height = bitmapSource.PixelHeight - y;

                    // Recortar el área deseada utilizando las coordenadas ajustadas
                    CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));

                    // Codificar el bitmap recortado a un MemoryStream
                    PngBitmapEncoder encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(croppedBitmap));

                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        encoder.Save(memoryStream);
                        memoryStream.Seek(0, SeekOrigin.Begin);

                        //ShowPreviewWindow(memoryStream);

                        // Cargar la imagen en Tesseract desde el MemoryStream
                        using (var img = Pix.LoadFromMemory(memoryStream.ToArray()))
                        using (var engine = new TesseractEngine(@"./Tesseract", "eng", EngineMode.Default))
                        {
                            var result = engine.Process(img);
                            return result.GetText();
                        }
                    }
                }
            }
            return "";
        }



        // All Pages Objects
        [NotifyPropertyChangedFor(nameof(Show_on_this_page))]
        [ObservableProperty]
        [property: Description("Enable this object to be used in all pages.")]
        [property: Category("Layout:")]
        private bool enable_on_all_pages;

        partial void OnEnable_on_all_pagesChanged(bool value)
        {
            if (!value)
                Show_on_this_page = true;
        }

        private List<string> Show_on_this_pages_oculto;

        private bool show_on_this_page;
        [property: Category("Layout:")]
        public bool Show_on_this_page
        {
            get
            {
                // Not global object
                if (!enable_on_all_pages)
                {
                    if (Show_on_this_pages_oculto != null)
                        Show_on_this_pages_oculto.Clear();
                    Show_on_this_pages_oculto = null;
                    show_on_this_page = true;
                }
                else
                {
                    // Global Object
                    if (Show_on_this_pages_oculto == null)
                        Show_on_this_pages_oculto = new List<string>();
                    if (_mainViewModel == null || _mainViewModel.SelectedImage == null) show_on_this_page = false;
                    else show_on_this_page = Show_on_this_pages_oculto.Contains(_mainViewModel.SelectedImage);
                }
                return show_on_this_page;
            }
            set
            {
                // global object?
                if (enable_on_all_pages)
                {
                    // Global Object
                    if (Show_on_this_pages_oculto == null)
                        Show_on_this_pages_oculto = new List<string>();
                    if (_mainViewModel == null || _mainViewModel.SelectedImage == null) return;

                    if (value && !Show_on_this_pages_oculto.Contains(_mainViewModel.SelectedImage))
                        Show_on_this_pages_oculto.Add(_mainViewModel.SelectedImage);
                    else if (!value && Show_on_this_pages_oculto.Contains(_mainViewModel.SelectedImage))
                        Show_on_this_pages_oculto.Remove(_mainViewModel.SelectedImage);
                }
                SetProperty(ref show_on_this_page, value);
            }
        }

        private async void TimerCallback(object state)
        {
            await Task.Delay(500); // Esperar 0.5 segundos antes de ejecutar la función
            Application.Current.Dispatcher.Invoke(() =>
            {
                // Realiza tus cambios en la interfaz de usuario aquí
                OnTimerAfterMovement();
            });
        }
        /// <summary>
        /// Este timer se usa para llamar a OnTimerAfterMovement luego de que han pasado 500ms sin que se
        /// llame a ResetTimer.
        /// Esto es util para no actualizar las simulaciones o controles mientras se esta moviendo aun el control.
        /// Se debe hacer overrride de OnTimerAfterMovement y dentro de esta funcion llamar a la actualizacion que se 
        /// requiere hacer una vez terminado el movimiento del control por parte del usuario.
        /// </summary>
        public void ResetTimer()
        {
            if (timer == null)
                timer = new System.Threading.Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);

            timer.Change(500, Timeout.Infinite);
        }

        public virtual void OnTimerAfterMovement() { }

        /// <summary>
        /// Usado para saber cuando un objeto fue creado manualmente o por algun generador
        /// Mas adelante la idea es poder eliminar o tratar de manera diferente a estos objetos
        /// </summary>
        public bool AutoCreated = false;

        /// <summary>
        /// Son los objetos marcados para ser eliminados luego de se detectados por un sensor del mundo fisico
        /// Se deben eliminar fuera de la simulacion
        /// </summary>
        public bool RemoverDesdeSimulacion = false; // La simulacion indica que se debe remover

        [JsonIgnore]
        private DataSaveToSerialize DataSave;

        /// <summary>
        /// Link al UserControl. Inicializado en el evento OnLoaded
        /// </summary>
        [JsonIgnore]
        protected UserControl? _visualRepresentation = null;

        /// <summary>
        /// Link al control PLC. 
        /// Inicializado en el metodo SetPLC
        /// </summary>
        [JsonIgnore]
        protected PLCModel? _plc = null;

        /// <summary>
        /// Se llama luego de cada Step. Esto permite actualziar las posiciones graficas de cada objeto que se esta dibujando
        /// con las nuevas posiciones calculadas luego de la simulacion fisica.
        /// </summary>
        /// <param name="elapsedMilliseconds"></param>
        public virtual void UpdateControl(int elapsedMilliseconds) { }

        /// <summary>
        /// Se llama antes de comenzar la simulacion con el boton de Iniciar simulacion.
        /// La idea es actualizar los objetos en el motor fisico antes de comenzar la simulacion fisica.
        /// </summary>
        public virtual void UpdateGeometryStart() { }

        /// <summary>
        /// Se llama al terminar la simulacion con el boton de Detener simulacion.
        /// Se puede utilizar para detener los storyboard o alguna otra simulacion independiente
        /// </summary>
        public virtual void SimulationStop() { }

        /// <summary>
        /// Se llama antes de cada Step al World de la simulacion fisica.
        /// Permite actualizar el estado de los objetos de la simulacion fisica
        /// </summary>
        public virtual void UpdateGeometryStep() { }

        /// <summary>
        /// Es llamada en cada Tick del reloj establecido del PLC
        /// cuando la conexion con el PLC esta establecida.
        /// </summary>
        /// <param name="plc"></param>
        /// <param name="elapsedMilliseconds"></param>
        public virtual void UpdatePLC(PLCModel plc, int elapsedMilliseconds) { }

        /// <summary>
        /// El UserControl ya se ha cargado y podemos obtener las coordenadas para 
        /// crear el objeto de simulacion
        /// </summary>
        public virtual void ucLoaded()
        {
            ActualizarLeftTop();
        }

        /// <summary>
        ///  El UserControl se esta eliminando
        ///  eliminar el objeto de simulacion
        /// </summary>
        public virtual void ucUnLoaded() { }

        [JsonIgnore]
        public MainViewModel _mainViewModel;

        /// <summary>
        /// Link al UserControl. Inicializado en el evento OnLoaded
        /// </summary>
        [JsonIgnore]
        public UserControl? VisualRepresentation
        {
            get => _visualRepresentation;
            set => _visualRepresentation = value;
        }

        /// <summary>
        /// Link al Simualdor fisico.
        /// </summary>
        [JsonIgnore]
        public SimulationManagerFP simulationManager;

        /// <summary>
        /// Prepara la clase para ser serializable poniendo a null los objetos que tienen referencias circulares
        /// Luego se debe llamar a RestaurarDatosNoSerializables para restaurar el estado original
        /// </summary>
        public void SalvarDatosNoSerializables()
        {
            DataSave = new DataSaveToSerialize(_mainViewModel, _visualRepresentation, simulationManager);
            _mainViewModel = null;
            _visualRepresentation = null;
            simulationManager = null;
        }

        /// <summary>
        /// Restaura los links de los objetos que se eliminaron temporalmente para serializar.
        /// </summary>
        public void RestaurarDatosNoSerializables()
        {
            if (DataSave == null) return;
            DataSave.DataRestoreAfterSerialize(out _mainViewModel, out _visualRepresentation, out simulationManager);
        }
        /// <summary>
        /// Se llama una unica vez al conectar con el PLC.
        /// </summary>
        /// <param name="plc"></param>
        public void SetPLC(PLCModel plc)
        {
            _plc = plc;
            UpdatePLCPrimerCiclo();
        }

        /// <summary>
        /// Se llama una unica vez al conectar con el PLC
        /// </summary>
        public virtual void UpdatePLCPrimerCiclo() { }

        /// <summary>
        /// Crea una conexion entre los osBase segun el NameLink sea igual al nombre de algun osBase siempre que se cumpla 
        /// con el requisito que el osBase encontrado sea de tipo tipoOsBase
        /// </summary>
        /// <param name="NameLink"></param>
        /// <param name="tipoOsBase"></param>
        /// <returns></returns>
        protected osBase ObtenerLink(string NameLink, Type tipoOsBase)
        {
            if (!string.IsNullOrEmpty(NameLink) && _mainViewModel != null)
            {
                foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
                {
                    if (tipoOsBase.IsInstanceOfType(objetoSimulable) && objetoSimulable.Nombre == NameLink)
                    {
                        return objetoSimulable;
                    }
                }
            }
            return null;
        }

        protected Storyboard CrearAnimacionMultiStoryBoardTrasnporte(Rectangle transporte, bool invertirDireccion)
        {
            if (_visualRepresentation == null) return null;
            if (transporte == null) return null;

            Storyboard storyboard = new Storyboard();
            var animation = new DoubleAnimation
            {
                From = invertirDireccion ? 20 : 0,
                To = invertirDireccion ? 0 : 20, // Total Pixels Brush
                Duration = TimeSpan.FromSeconds(PixelToMeter.Instance.calc.PixelsToMeters(20) * 60),
                RepeatBehavior = RepeatBehavior.Forever
            };
            Storyboard.SetTarget(animation, transporte);
            Storyboard.SetTargetProperty(animation, new PropertyPath("(Rectangle.Fill).(VisualBrush.Transform).(TransformGroup.Children)[0].(TranslateTransform.X)"));
            storyboard.Children.Add(animation);
            storyboard.Begin();
            storyboard.SetSpeedRatio(0);
            return storyboard;
        }

        protected Storyboard CrearAnimacionMultiStoryBoardTrasnporte(Storyboard storyboard, Rectangle transporte, bool invertirDireccion)
        {
            // Detener y eliminar el storyboard existente si hay uno
            if (storyboard != null)
            {
                storyboard.Stop();
                storyboard.Children.Clear();
            }

            return CrearAnimacionMultiStoryBoardTrasnporte(transporte, invertirDireccion);
        }

        protected void ActualizarAnimacionMultiStoryBoardTransporte(Storyboard storyboard, float velocidadActual)
        {
            if (_visualRepresentation == null) return;
            if (storyboard == null) return;

            if (!_mainViewModel.IsSimulationRunning)
                storyboard.SetSpeedRatio(0);
            else
                storyboard.SetSpeedRatio(Math.Abs(velocidadActual));
        }

        protected void CrearAnimacionStoryBoardTrasnporte(Rectangle transporte, bool invertirDireccion)
        {
            _storyboard = CrearAnimacionMultiStoryBoardTrasnporte(_storyboard, transporte, invertirDireccion);
        }

        protected void ActualizarAnimacionStoryBoardTransporte(float velocidadActual)
        {
            ActualizarAnimacionMultiStoryBoardTransporte(_storyboard, velocidadActual);
        }

        /// <summary>
        /// Actualiza Left Top del objeto respecto al Canvas padre.
        /// </summary>
        public void ActualizarLeftTop()
        {
            CanvasSetLeftinMeter(Left);
            CanvasSetTopinMeter(Top);
        }

        public bool LeerBitTag(string Tag)
        {
            if (this._plc == null) return false;
            if (!string.IsNullOrEmpty(Tag))
            {
                if (Tag == "1") return true;
                else if (Tag == "0") return false;
                if (_plc != null)
                    return _plc.LeerTagBool(Tag);
            }
            return false;
        }

        /// <summary>
        /// Si la conexion con el plc esta activa y el Tag no es nulo escribe el bit en el PLC
        /// </summary>
        /// <param name="Tag"></param>
        /// <param name="Value"></param>
        public void EscribirBitTag(string Tag, bool Value)
        {
            if (_plc == null) return;
            if (!string.IsNullOrEmpty(Tag))
                if (_plc != null)
                    _plc.EscribirTagBool(Tag, Value);
        }

        public void EscribirWordTagScaled(string Tag, float Value, float IN_scale_Min, float IN_scale_Max, float OUT_scale_Min, float OUT_scale_Max)
        {
            if (_plc == null) return;
            if (!string.IsNullOrEmpty(Tag))
            {
                SDataValue plcData = new SDataValue();
                plcData.UInt16 = (ushort)((Value - IN_scale_Min) / (IN_scale_Max - IN_scale_Min) * (OUT_scale_Max - OUT_scale_Min) + OUT_scale_Min);
                _plc.EscribirTag(Tag, plcData);
            }
        }

        public float LeerWordTagScaled(string Tag, float IN_scale_Min = 0, float IN_scale_Max = 27648, float OUT_scale_Min = 0, float OUT_scale_Max = 100)
        {
            if (_plc == null) return 0;
            if (!string.IsNullOrEmpty(Tag))
            {
                if (float.TryParse(Tag, out float v))
                    return v;
                if (_plc != null)
                {
                    SDataValue plcData = _plc.LeerTag(Tag);
                    float Value = plcData.UInt16; // WORD
                    return (Value - IN_scale_Min) / (IN_scale_Max - IN_scale_Min) * (OUT_scale_Max - OUT_scale_Min) + OUT_scale_Min;
                }
            }
            return 0;
        }
        public float LeerWordTag(string Tag)
        {
            if (_plc == null) return 0;
            if (!string.IsNullOrEmpty(Tag))
            {
                if (float.TryParse(Tag, out float v))
                    return v;
                if (_plc != null)
                {
                    SDataValue plcData = _plc.LeerTag(Tag);
                    float Value = plcData.UInt16; // WORD
                    return Value;
                }
            }
            return 0;
        }

        public void CanvasSetLeftinMeter(float left)
        {
            if (_visualRepresentation != null)
                Canvas.SetLeft(_visualRepresentation, PixelToMeter.Instance.calc.MetersToPixels(left));
        }
        public float CanvasGetLeftinMeter()
        {
            if (_visualRepresentation != null)
                return PixelToMeter.Instance.calc.PixelsToMeters((float)Canvas.GetLeft(_visualRepresentation));
            else return 0f;
        }

        public void CanvasSetTopinMeter(float top)
        {
            if (_visualRepresentation != null)
                Canvas.SetTop(_visualRepresentation, PixelToMeter.Instance.calc.MetersToPixels(top));
        }
        public float CanvasGetTopinMeter()
        {
            if (_visualRepresentation != null)
                return PixelToMeter.Instance.calc.PixelsToMeters((float)System.Windows.Controls.Canvas.GetTop(_visualRepresentation));
            else return 0f;
        }

        public float PixelsToMeters(float pixel)
        {
            return PixelToMeter.Instance.calc.PixelsToMeters(pixel);
        }

        public Vector2 PixelsToMeters(Vector2 pixel)
        {
            return new Vector2(PixelsToMeters(pixel.X), PixelsToMeters(pixel.Y));
        }

        public float MeterToPixels(float meter)
        {
            return PixelToMeter.Instance.calc.MetersToPixels(meter);
        }

        public Vector2 MeterToPixels(Vector2 meter)
        {
            return new Vector2(MeterToPixels(meter.X), MeterToPixels(meter.Y));
        }


        private static GeneralTransform GetTransformToRoot(Visual visual)
        {
            // Crear una transformación acumulativa inicial
            GeneralTransformGroup transformGroup = new GeneralTransformGroup();

            // Recorrer hacia arriba el árbol visual, acumulando las transformaciones
            while (visual != null)
            {
                GeneralTransform transform = visual.TransformToAncestor(visual);
                transformGroup.Children.Insert(0, transform);
                visual = VisualTreeHelper.GetParent(visual) as Visual;
            }

            return transformGroup;
        }


        public (Vector2 TopLeft, Vector2 BottomRight) GetRectangleCoordinatesInMeter(Rectangle rect)
        {
            if (rect != null)
            {
                var _canvasLeft = CanvasGetLeftinMeter();
                var _canvasTop = CanvasGetTopinMeter();

                // Obtiene la transformada del objeto visual
                GeneralTransform transform = rect.TransformToAncestor(_visualRepresentation);

                // Obtiene la posición absoluta
                Point topLeft = transform.Transform(new Point(0, 0));
                Point bottomRight = transform.Transform(new Point(rect.ActualWidth, rect.ActualHeight));

                return (new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.Y) + _canvasTop),
                        new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.Y) + _canvasTop));
            }
            else return (new Vector2(0, 0), new Vector2(0, 0));
        }

        public (Vector2 Start, Vector2 End) GetCenterLineVectors(Rectangle rect)
        {
            if (rect == null)
                return (new Vector2(0, 0), new Vector2(0, 0));

            Vector2 start = new Vector2(0, 0);
            Vector2 end = new Vector2(0, 0);

            // Usar Dispatcher para asegurar la ejecución en el hilo correcto
            _visualRepresentation?.Dispatcher.Invoke(() =>
            {
                // Asegúrate de que el control está en el árbol visual y actualizado
                if (_visualRepresentation.IsLoaded && rect.IsLoaded)
                {
                    _visualRepresentation.UpdateLayout();

                    var _canvasLeft = CanvasGetLeftinMeter();
                    var _canvasTop = CanvasGetTopinMeter();

                    var transform = rect.TransformToAncestor(_visualRepresentation);

                    // Puntos en coordenadas locales del rectángulo no rotado
                    Point startLocal = new Point(0, rect.ActualHeight / 2);
                    Point endLocal = new Point(rect.ActualWidth, rect.ActualHeight / 2);

                    // Transformar estos puntos al sistema de coordenadas del ancestro
                    Point transformedStart = transform.Transform(startLocal);
                    Point transformedEnd = transform.Transform(endLocal);

                    // Convierte a unidades de Farseer (metros en este caso)
                    start = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.Y) + _canvasTop);
                    end = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.Y) + _canvasTop);
                }
            });

            return (start, end);
        }


        public Vector2 GetCurveCenterInMeter(float RadioExterno)
        {
            var _canvasLeft = CanvasGetLeftinMeter();
            var _canvasTop = CanvasGetTopinMeter();

            // El centro del Canvas
            double centerX = RadioExterno + _canvasLeft;
            double centerY = RadioExterno + _canvasTop;

            // Convertir a Vector2
            return new Vector2((float)centerX, (float)centerY);
        }

        public Vector2 GetRectangleCenter(Rectangle wpfRect)
        {
            var coords = GetRectangleCoordinatesInMeter(wpfRect);

            Vector2 topLeft = coords.TopLeft;
            Vector2 bottomRight = coords.BottomRight;

            // Calcular el centro, ancho y alto en metros
            return new Vector2((topLeft.X + bottomRight.X) / 2, (topLeft.Y + bottomRight.Y) / 2);
        }



        public void UpdateRectangle(simTransporte simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
        {
            if (simRect != null)
                simRect.Create(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo);
        }

        public void UpdateRectangle(simBarrera simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
        {
            if (simRect != null)
                simRect.Create(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo);
        }

        public void UpdateCurve(simCurve curva, float RadioInterno, float RadioExterno, float startAngle, float endAngle)
        {
            curva.Create(RadioInterno, RadioExterno, startAngle, endAngle, GetCurveCenterInMeter(RadioExterno));
        }

        public simCurve AddCurve(float RadioInterno, float RadioExterno, float startAngle, float endAngle)
        {
            return simulationManager.AddCurve(RadioInterno, RadioExterno, startAngle, endAngle, GetCurveCenterInMeter(RadioExterno));
        }

        public simTransporte AddRectangle(SimulationManagerFP simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
        {
            return simulationManager.AddRectangle(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo);
        }

        public simBarrera AddBarrera(SimulationManagerFP simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo, bool detectarCuello)
        {
            return simulationManager.AddBarrera(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo, detectarCuello);
        }

        public void UpdateOrCreateLine(simGuia simGuia, Rectangle wpfRect)
        {
            if (simGuia != null)
            {

                var coords = GetCenterLineVectors(wpfRect);

                // Crear o actualizar simRectangle
                simGuia.Create(coords.Start, coords.End); // asumiendo que el ángulo inicial es 0
            }
        }

        public simGuia AddLine(SimulationManagerFP simulationManager, Rectangle wpfRect)
        {
            var coords = GetCenterLineVectors(wpfRect);
            return simulationManager.AddLine(coords.Start, coords.End);
        }

        public ImageSource ImageFromPath(string value)
        {
            if (value is string stringValue)
            {
                return new BitmapImage(new Uri(stringValue, UriKind.RelativeOrAbsolute));
            }
            return null;
        }

        static protected T GetLastElement<T>(List<T> list) where T : class
        {
            if (list.Count > 0)
            {
                return list[list.Count - 1];
            }
            else
            {
                return null;
            }
        }

    }

    public class UniqueId
    {
        public int Value { get; set; }
        public UniqueId ObtenerNuevaID()
        {
            Value = EstadoPersistente.Instance.newid;
            if (Value == 0)
                Value++;
            return this;
        }
        public void NoId()
        {
            // No ID == NULL
            Value = 0;
        }
    }

    public class osBaseItemsSource<T> : IItemsSource
    {
        public ItemCollection GetValues()
        {
            ItemCollection items = new ItemCollection();

            var viewModel = (MainViewModel)App.Current.MainWindow.DataContext;
            var objetosSimulables = viewModel.ObjetosSimulables;

            items.Add("");
            foreach (var obj in objetosSimulables)
            {
                if (obj.GetType() == typeof(T))
                {
                    items.Add(obj.Nombre);
                }
            }

            return items;
        }
    }

    public class TimerTON_TOFF
    {
        Stopwatch _stopwatch_ON = new Stopwatch();
        Stopwatch _stopwatch_OFF = new Stopwatch();
        private bool _senalFiltrada;

        public float Tiempo_ON_s { get; set; }
        public float Tiempo_OFF_s { get; set; }

        private bool senal;
        public bool Senal
        {
            get => senal;
            set
            {
                senal = value;
                if (value)
                {
                    ReiniciarTimer(_stopwatch_ON);
                    _stopwatch_OFF.Reset();
                }
                else
                {
                    _stopwatch_ON.Reset();
                    ReiniciarTimer(_stopwatch_OFF);
                }
            }
        }

        public TimerTON_TOFF()
        {
            Senal = false;
            Tiempo_ON_s = 1;
            Tiempo_OFF_s = 1;
        }

        public bool SenalFiltrada()
        {
            if (_stopwatch_ON.ElapsedMilliseconds > (Tiempo_ON_s * 1000))
                _senalFiltrada = true;
            if (_stopwatch_OFF.ElapsedMilliseconds > (Tiempo_OFF_s * 1000))
                _senalFiltrada = false;

            return _senalFiltrada;
        }

        void ReiniciarTimer(Stopwatch timer)
        {
            timer.Reset();
            timer.Start();
        }

    }

    [AttributeUsage(AttributeTargets.Property)]
    public class HiddenAttribute : Attribute
    {
    }
}
