using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using CtrEditor.Simulacion;
using LibS7Adv;
using nkast.Aether.Physics2D.Common;
using Siemens.Simatic.Simulation.Runtime;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Tesseract;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Application = System.Windows.Application;
using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection;
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
using UserControl = System.Windows.Controls.UserControl;

namespace CtrEditor.ObjetosSim
{

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

    public interface IDataContainer
    {
        [JsonIgnore]
        osBase? Datos { get; set; }
        int zIndex_fromFrames { get; set; }
        void Highlight(bool State);
        ZIndexEnum ZIndex_Base();
    }

    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;

        public void CheckData()
        {
            if (Id is null)
                Id = new UniqueId().ObtenerNuevaID();
        }

        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) { }


        [ObservableProperty]
        [property: Description("Widht.")]
        [property: Category("Layout:")]
        private float ancho;

        partial void OnAnchoChanged(float value)
        {
            AnchoChanged(value);
        }
        public virtual void AnchoChanged(float value) { }

        [ObservableProperty]
        [property: Description("Height.")]
        [property: Category("Layout:")]
        private float alto;

        partial void OnAltoChanged(float value)
        {
            AltoChanged(value);
        }
        public virtual void AltoChanged(float value) { }


        [ObservableProperty]
        [property: Description("Angle.")]
        [property: Category("Layout:")]
        private float angulo;

        partial void OnAnguloChanged(float value)
        {
            AnguloChanged(value);
        }
        public virtual void AnguloChanged(float value) { }


        public void Resize(double width, double height)
        {
            Resize((float)width, (float)height);
        }

        public void Resize(float Delta_W_pixels, float Delta_H_pixels)
        {
            var Delta_W = PixelToMeter.Instance.calc.PixelsToMeters(Delta_W_pixels);
            var Delta_H = PixelToMeter.Instance.calc.PixelsToMeters(Delta_H_pixels);

            OnResize(Delta_W, Delta_H);
            OnMoveResizeRotate();

            if ((Angulo >= 45 && Angulo <= 135) || (Angulo >= 225 && Angulo <= 315))
            {
                Ancho += Delta_H;
                Alto += Delta_W;
            }
            else
            {
                Ancho += Delta_W;
                Alto += Delta_H;
            }
            if (Ancho < 0.01f) Ancho = 0.01f;
            if (Alto < 0.01f) Alto = 0.01f;
        }
        public virtual void OnResize(float Delta_Width, float Delta_Height) { }

        public void Move(double LeftPixels, double TopPixels)
        {
            Move((float)LeftPixels, (float)TopPixels);
        }
        public void Move(float LeftPixels, float TopPixels)
        {
            Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
            Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
            OnMove(LeftPixels, TopPixels);
            OnMoveResizeRotate();
        }
        public virtual void OnMove(float LeftPixels, float TopPixels) { }

        public void Rotate(double Delta_Angle)
        {
            Rotate((float)Delta_Angle);
        }
        public void Rotate(float Delta_Angle)
        {
            Angulo += Delta_Angle;
            if (Angulo <= -360)
                Angulo += 360;
            if (Angulo >= 360)
                Angulo -= 360;

            OnRotate(Delta_Angle);
            OnMoveResizeRotate();
        }
        public virtual void OnRotate(float Delta_Angle) { }

        public virtual void OnMoveResizeRotate() { }

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

        // Group as FacePlate
        [ObservableProperty]
        [property: Description("This is a link to a faceplate. It works like an anchor.")]
        [property: Category("Group:")]
        [property: ItemsSource(typeof(osBaseItemsSource<osTextPlate>))]
        private string group_Panel;

        [JsonIgnore]
        private osTextPlate TextPlate;
        [JsonIgnore]
        private bool isUpdatingFromTextPlate = false;

        partial void OnGroup_PanelChanged(string value)
        {
            if (TextPlate != null)
                TextPlate.PropertyChanged -= OnTextPlatePropertyChanged;
            if (_mainViewModel != null && value != null && value.Length > 0)
            {
                TextPlate = (osTextPlate)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osTextPlate && s.Nombre == value), null);
                if (TextPlate != null)
                    TextPlate.PropertyChanged += OnTextPlatePropertyChanged;
            }
        }

        private void OnTextPlatePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!isUpdatingFromTextPlate)
            {
                isUpdatingFromTextPlate = true;

                if (e.PropertyName == nameof(osTextPlate.Nombre))
                    Group_Panel = ((osTextPlate)sender).Nombre;

                if (e.PropertyName == nameof(osTextPlate.Top))
                    Top += ((osTextPlate)sender).offsetY;

                if (e.PropertyName == nameof(osTextPlate.Left))
                    Left += ((osTextPlate)sender).offsetX;

                isUpdatingFromTextPlate = false;
            }
        }

        // Group as FacePlate
        [ObservableProperty]
        [property: Description("This is a link to a FromPlate that moves automatically. It works like an anchor.")]
        [property: Category("Group:")]
        [property: ItemsSource(typeof(osBaseItemsSource<osFramePlate>))]
        private string group_FramePanel;

        [JsonIgnore]
        private osFramePlate FramePlate;
        [JsonIgnore]
        private bool isUpdatingFromFramePlate = false;

        partial void OnGroup_FramePanelChanged(string value)
        {
            if (FramePlate != null)
                FramePlate.PropertyChanged -= OnFramePlatePropertyChanged;
            if (_mainViewModel != null && value != null && value.Length > 0)
            {
                FramePlate = (osFramePlate)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osFramePlate && s.Nombre == value), null);
                if (FramePlate != null)
                {
                    FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
                    UpdateZIndex(FramePlate.Zindex_FramePlate);
                }
            }
        }

        protected void UpdateZIndex(int zIndex)
        {
            if (_visualRepresentation is IDataContainer dataContainer)
            {
                dataContainer.zIndex_fromFrames = zIndex;
                Canvas.SetZIndex(_visualRepresentation, zIndex + ((int)dataContainer.ZIndex_Base()));
            }
        }

        private void OnFramePlatePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!isUpdatingFromFramePlate)
            {
                isUpdatingFromFramePlate = true;

                if (e.PropertyName == nameof(osFramePlate.Nombre))
                    Group_Panel = ((osFramePlate)sender).Nombre;

                if (e.PropertyName == nameof(osFramePlate.Top))
                {
                    Top += ((osFramePlate)sender).offsetY;
                    OnMoveResizeRotate();
                }

                if (e.PropertyName == nameof(osFramePlate.Left))
                {
                    Left += ((osFramePlate)sender).offsetX;
                    OnMoveResizeRotate();
                }

                if (e.PropertyName == nameof(osFramePlate.Zindex_FramePlate))
                    UpdateZIndex(((osFramePlate)sender).Zindex_FramePlate);

                isUpdatingFromFramePlate = false;
            }
        }


        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, float Angulo = 0, bool ShowPreview = false)
        {
            if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
            {
                if (imagenDeFondo.Source is BitmapSource bitmapSource)
                {
                    float originalDpiX = (float)bitmapSource.DpiX;
                    float originalDpiY = (float)bitmapSource.DpiY;

                    float canvasDpiX = 96;
                    float canvasDpiY = 96;

                    float scaleFactorX = originalDpiX / canvasDpiX;
                    float scaleFactorY = originalDpiY / canvasDpiY;

                    int x = (int)MeterToPixels(Left * scaleFactorX);
                    int y = (int)MeterToPixels(Top * scaleFactorY);
                    int width = (int)MeterToPixels(Ancho * scaleFactorX);
                    int height = (int)MeterToPixels(Alto * scaleFactorY);

                    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;

                    CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));

                    TransformedBitmap transformedBitmap = new TransformedBitmap();
                    transformedBitmap.BeginInit();
                    transformedBitmap.Source = croppedBitmap;

                    if (Angulo != 0)
                    {
                        RotateTransform rotateTransform = new RotateTransform(-Angulo);
                        transformedBitmap.Transform = rotateTransform;
                    }

                    transformedBitmap.EndInit();

                    PngBitmapEncoder encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(transformedBitmap));

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

                        if (ShowPreview) ShowPreviewWindow(memoryStream);

                        using (var bmp = new System.Drawing.Bitmap(memoryStream))
                        {
                            int targetDpi = 400;
                            var resizedBmp = new System.Drawing.Bitmap(bmp, new System.Drawing.Size(bmp.Width * targetDpi / (int)originalDpiX, bmp.Height * targetDpi / (int)originalDpiY));

                            using (var msResized = new MemoryStream())
                            {
                                resizedBmp.Save(msResized, System.Drawing.Imaging.ImageFormat.Png);
                                msResized.Seek(0, SeekOrigin.Begin);

                                using (var img = Pix.LoadFromMemory(msResized.ToArray()))
                                using (var engine = new TesseractEngine(@"./Tesseract", "eng", EngineMode.Default))
                                {
                                    // Configuraciones para mejorar el OCR de una sola letra
                                    engine.SetVariable("tessedit_char_whitelist", " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-."); // Lista blanca de caracteres
                                    var result = engine.Process(img);
                                    return result.GetText();
                                }
                            }
                        }
                    }
                }
            }
            return "";
        }

        // Método para obtener un color más claro y saturado
        public static Color ObtenerColorMasClaroYSaturado(Color colorOriginal, double incrementoL, double incrementoS)
        {
            return ColorUtils.ObtenerColorMasClaroYSaturado(colorOriginal, incrementoL, incrementoS);
        }

        // Método para obtener un color más claro
        public Color ObtenerColorMasClaro(Color colorOriginal, double incremento)
        {
            incremento = Math.Clamp(incremento, 0, 1); // Asegurarse de que el incremento esté entre 0 y 1
            byte r = (byte)(colorOriginal.R + (255 - colorOriginal.R) * incremento);
            byte g = (byte)(colorOriginal.G + (255 - colorOriginal.G) * incremento);
            byte b = (byte)(colorOriginal.B + (255 - colorOriginal.B) * incremento);
            return Color.FromArgb(colorOriginal.A, r, g, b);
        }

        // 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;
        }

        [ObservableProperty]
        [property: Hidden]
        private List<string> showOnThisPagesList;

        private bool show_On_This_Page;
        [property: Category("Layout:")]
        public bool Show_On_This_Page
        {
            get
            {
                if (!Enable_On_All_Pages)
                {
                    showOnThisPagesList?.Clear();
                    showOnThisPagesList = null;
                    show_On_This_Page = true;
                }
                else
                {
                    if (showOnThisPagesList == null)
                    {
                        showOnThisPagesList = new List<string> { _mainViewModel?.SelectedImage };
                    }
                    show_On_This_Page = _mainViewModel?.SelectedImage != null && showOnThisPagesList.Contains(_mainViewModel.SelectedImage);
                }
                return show_On_This_Page;
            }
            set
            {
                if (Enable_On_All_Pages)
                {
                    if (showOnThisPagesList == null)
                    {
                        showOnThisPagesList = new List<string>();
                    }
                    if (_mainViewModel?.SelectedImage == null) return;

                    if (value)
                    {
                        if (!showOnThisPagesList.Contains(_mainViewModel.SelectedImage))
                        {
                            showOnThisPagesList.Add(_mainViewModel.SelectedImage);
                        }
                    }
                    else
                    {
                        showOnThisPagesList.Remove(_mainViewModel.SelectedImage);
                    }
                }
                SetProperty(ref show_On_This_Page, value);
            }
        }

        [ObservableProperty]
        [property: Description("Autocreated and cloned with Search Templates")]
        [property: Category("Tag Extraction:")]
        bool cloned;

        [ObservableProperty]
        [property: Description("Autocreated and cloned with Search Templates")]
        [property: Category("Tag Extraction:")]
        [property: Hidden]
        UniqueId cloned_from;

        [ObservableProperty]
        private bool isSelected;

        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 PLCViewModel? _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(PLCViewModel 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();
            OnGroup_PanelChanged(Group_Panel); // Establece el link y se suscribe a los eventos
            OnGroup_FramePanelChanged(Group_FramePanel); // Establece el link y se suscribe a los eventos
            Show_On_This_Page = Show_On_This_Page; // Update data
        }

        /// <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(PLCViewModel plc)
        {
            _plc = plc;
            UpdatePLCPrimerCiclo();
        }

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

        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.EscribirBool(Tag, Value);
        }

        public void EscribirDINTTag(string Tag, float Value)
        {
            if (_plc == null) return;
            if (!string.IsNullOrEmpty(Tag))
            {
                SDataValue plcData = new SDataValue();
                plcData.Int32 = (int)Value;
                _plc.EscribirTag(Tag, plcData);
            }
        }

        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;
            }
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);

            // Don't mark changes for certain properties
            if (e.PropertyName != nameof(Top) &&
                e.PropertyName != nameof(Left) &&
                e.PropertyName != nameof(Ancho) &&
                e.PropertyName != nameof(Angulo) &&
                Cloned == false &&
                e.PropertyName != nameof(Show_On_This_Page) &&
                _mainViewModel != null)
            {
                _mainViewModel.HasUnsavedChanges = true;
            }
        }

    }

    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;
        }

        // Sobrecarga del operador ++
        public static UniqueId operator ++(UniqueId id)
        {
            if (id == null)
            {
                id = new UniqueId();
            }

            id.Value = EstadoPersistente.Instance.newid;
            if (id.Value == 0)
                id.Value++;

            return id;
        }

        // Sobrecarga del operador ==
        public static bool operator ==(UniqueId id1, UniqueId id2)
        {
            if (ReferenceEquals(id1, null) || ReferenceEquals(id2, null))
                return false;

            return id1.Value == id2.Value;
        }

        // Sobrecarga del operador !=
        public static bool operator !=(UniqueId id1, UniqueId id2)
        {
            if (ReferenceEquals(id1, null) || ReferenceEquals(id2, null))
                return true;

            return id1.Value != id2.Value;
        }

        // Sobrescribir Equals y GetHashCode también es una buena práctica
        public override bool Equals(object obj)
        {
            if (obj is UniqueId id)
            {
                return Value == id.Value;
            }
            return false;
        }

        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }
    }



    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);
                    StopTimer(_stopwatch_OFF);
                }
                else
                {
                    ReiniciarTimer(_stopwatch_OFF);
                    StopTimer(_stopwatch_ON);
                }
            }
        }

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

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

            return _senalFiltrada;
        }

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

    }

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

    public class ColorUtils
    {
        // Convertir RGB a HSL
        public static void RgbToHsl(Color color, out double h, out double s, out double l)
        {
            double r = color.R / 255.0;
            double g = color.G / 255.0;
            double b = color.B / 255.0;

            double max = Math.Max(r, Math.Max(g, b));
            double min = Math.Min(r, Math.Min(g, b));

            h = s = l = (max + min) / 2.0;

            if (max == min)
            {
                h = s = 0.0; // Gris
            }
            else
            {
                double d = max - min;
                s = l > 0.5 ? d / (2.0 - max - min) : d / (max + min);

                if (max == r)
                    h = (g - b) / d + (g < b ? 6.0 : 0.0);
                else if (max == g)
                    h = (b - r) / d + 2.0;
                else
                    h = (r - g) / d + 4.0;

                h /= 6.0;
            }
        }

        // Convertir HSL a RGB
        public static Color HslToRgb(double h, double s, double l)
        {
            double r, g, b;

            if (s == 0.0)
            {
                r = g = b = l; // Gris
            }
            else
            {
                Func<double, double, double, double> hue2rgb = (p, q, t) =>
                {
                    if (t < 0.0) t += 1.0;
                    if (t > 1.0) t -= 1.0;
                    if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;
                    if (t < 1.0 / 2.0) return q;
                    if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
                    return p;
                };

                double q = l < 0.5 ? l * (1.0 + s) : l + s - l * s;
                double p = 2.0 * l - q;

                r = hue2rgb(p, q, h + 1.0 / 3.0);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1.0 / 3.0);
            }

            return Color.FromArgb(255, (byte)(r * 255.0), (byte)(g * 255.0), (byte)(b * 255.0));
        }

        // Método para obtener un color más claro y saturado
        public static Color ObtenerColorMasClaroYSaturado(Color colorOriginal, double incrementoL, double incrementoS)
        {
            RgbToHsl(colorOriginal, out double h, out double s, out double l);

            l = Math.Clamp(l + incrementoL, 0.0, 1.0);
            s = Math.Clamp(s + incrementoS, 0.0, 1.0);

            return HslToRgb(h, s, l);
        }
    }

}
