using System; using System.Collections.ObjectModel; using System.IO; using System.Threading.Tasks; using Newtonsoft.Json; using System.Linq; using System.Windows.Controls; using System.Windows; using CtrEditor.ObjetosSim; using LibS7Adv; namespace CtrEditor { /// <summary> /// Metadata de una página, incluyendo información sobre la imagen y configuración /// </summary> public class PageMetadata { public string PageId { get; set; } public string PageName { get; set; } public string ImagePath { get; set; } public DateTime LastModified { get; set; } public Dictionary<string, object> CustomMetadata { get; set; } = new(); } /// <summary> /// Estado de un objeto dentro de una página específica /// </summary> public class PageObjectState { public string GlobalObjectId { get; set; } public bool IsVisible { get; set; } = true; public float Left { get; set; } public float Top { get; set; } public float Rotation { get; set; } public Dictionary<string, object> CustomProperties { get; set; } = new(); } /// <summary> /// Estado de una página específica /// </summary> public class PageState { public string PageId { get; set; } public string PageName { get; set; } public List<osBase> LocalObjects { get; set; } = new(); public Dictionary<string, PageObjectState> GlobalObjectStates { get; set; } = new(); public Dictionary<string, object> PageMetadata { get; set; } = new(); } /// <summary> /// Estado global de la aplicación /// </summary> public class GlobalState { public List<osBase> SharedObjects { get; set; } = new(); public Dictionary<string, PageMetadata> PagesMetadata { get; set; } = new(); public PLCViewModel PLCConfiguration { get; set; } public UnitConverter UnitConverter { get; set; } public int LastUsedId { get; set; } } /// <summary> /// Gestor principal de estados de la aplicación /// </summary> public class StateManager : IDisposable { private readonly string _basePath; private GlobalState _globalState; private Dictionary<string, PageState> _loadedPages; private string _currentPageId; private readonly object _lockObject = new(); private bool _hasUnsavedChanges; private MainViewModel _mainViewModel; public event EventHandler<string> PageStateChanged; public event EventHandler GlobalStateChanged; public StateManager(string basePath, MainViewModel mainViewModel) { _basePath = basePath; _mainViewModel = mainViewModel; _loadedPages = new Dictionary<string, PageState>(); Directory.CreateDirectory(basePath); } public async Task InitializeAsync() { await LoadGlobalStateAsync(); if (_mainViewModel.SelectedImage != null) { await LoadPageStateAsync(_mainViewModel.SelectedImage); } } private JsonSerializerSettings GetSerializerSettings() { return new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, ObjectCreationHandling = ObjectCreationHandling.Replace, Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }; } public async Task LoadGlobalStateAsync() { var path = Path.Combine(_basePath, "global.json"); if (File.Exists(path)) { try { var json = await File.ReadAllTextAsync(path); _globalState = JsonConvert.DeserializeObject<GlobalState>(json, GetSerializerSettings()); // Restaurar configuración global _mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel(); if (_globalState.UnitConverter != null) PixelToMeter.Instance.calc = _globalState.UnitConverter; else PixelToMeter.Instance.calc = new UnitConverter(1.0f); // Valor por defecto // Restaurar objetos globales foreach (var obj in _globalState.SharedObjects) { await RestoreObjectAsync(obj); } } catch (Exception ex) { // Log error y crear nuevo estado global _globalState = new GlobalState(); } } else { _globalState = new GlobalState(); } } public async Task SaveGlobalStateAsync() { var path = Path.Combine(_basePath, "global.json"); var backupPath = Path.ChangeExtension(path, ".bak"); // Actualizar estado global _globalState.PLCConfiguration = _mainViewModel.PLCViewModel; _globalState.UnitConverter = PixelToMeter.Instance.calc; // Crear backup if (File.Exists(path)) { File.Copy(path, backupPath, true); } try { // Preparar objetos para serialización foreach (var obj in _globalState.SharedObjects) { obj.SalvarDatosNoSerializables(); } var json = JsonConvert.SerializeObject(_globalState, GetSerializerSettings()); await File.WriteAllTextAsync(path, json); _hasUnsavedChanges = false; GlobalStateChanged?.Invoke(this, EventArgs.Empty); } finally { // Restaurar estado de objetos foreach (var obj in _globalState.SharedObjects) { obj.RestaurarDatosNoSerializables(); } } } public async Task<PageState> LoadPageStateAsync(string pageId) { // Retornar página cacheada si existe if (_loadedPages.TryGetValue(pageId, out var cachedState)) { return cachedState; } var path = Path.Combine(_basePath, $"page_{pageId}.json"); try { PageState pageState; if (File.Exists(path)) { var json = await File.ReadAllTextAsync(path); pageState = JsonConvert.DeserializeObject<PageState>(json, GetSerializerSettings()); } else { pageState = new PageState { PageId = pageId, PageName = _globalState.PagesMetadata.GetValueOrDefault(pageId)?.PageName ?? pageId }; } _loadedPages[pageId] = pageState; // Restaurar objetos locales foreach (var obj in pageState.LocalObjects) { await RestoreObjectAsync(obj); } // Aplicar estados de objetos globales ApplyGlobalObjectStates(pageState); return pageState; } catch (Exception ex) { // Log error throw; } } private void ApplyGlobalObjectStates(PageState pageState) { foreach (var kvp in pageState.GlobalObjectStates) { var globalObj = _globalState.SharedObjects.FirstOrDefault(o => o.Id.Value.ToString() == kvp.Key); if (globalObj != null) { var state = kvp.Value; globalObj.Left = state.Left; globalObj.Top = state.Top; globalObj.Angulo = state.Rotation; // Actualizar Show_On_This_Page que ahora maneja internamente showOnThisPagesList if (state.IsVisible && !globalObj.Show_On_This_Page) globalObj.Show_On_This_Page = true; else if (!state.IsVisible && globalObj.Show_On_This_Page) globalObj.Show_On_This_Page = false; // Aplicar propiedades personalizadas foreach (var prop in state.CustomProperties) { var property = globalObj.GetType().GetProperty(prop.Key); if (property != null && property.CanWrite) { property.SetValue(globalObj, prop.Value); } } } } } private async Task RestoreObjectAsync(osBase obj) { if (obj != null) { obj.CheckData(); await Task.Run(() => { Application.Current.Dispatcher.Invoke(() => { CrearUserControlDesdeObjetoSimulable(obj); }); }); } } private bool CrearUserControlDesdeObjetoSimulable(osBase osObjeto) { Type tipoObjeto = osObjeto.GetType(); UserControl userControl = UserControlFactory.GetControlForType(tipoObjeto); if (userControl != null) { UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager); osObjeto._mainViewModel = _mainViewModel; if (osObjeto.Id == null) { osObjeto.Id = new UniqueId().ObtenerNuevaID(); } _mainViewModel.MainWindow.AgregarRegistrarUserControlCanvas(userControl); return true; } return false; } public async Task SavePageStateAsync(string pageId) { if (!_loadedPages.TryGetValue(pageId, out var pageState)) return; var path = Path.Combine(_basePath, $"page_{pageId}.json"); var backupPath = Path.ChangeExtension(path, ".bak"); // Crear backup if (File.Exists(path)) { File.Copy(path, backupPath, true); } // Actualizar estado de objetos globales pageState.GlobalObjectStates.Clear(); foreach (var obj in _mainViewModel.ObjetosSimulables.Where(o => o.Enable_On_All_Pages)) { var currentPageId = _mainViewModel.SelectedImage; pageState.GlobalObjectStates[obj.Id.Value.ToString()] = new PageObjectState { GlobalObjectId = obj.Id.Value.ToString(), IsVisible = obj.Show_On_This_Page, Left = obj.Left, Top = obj.Top, Rotation = obj.Angulo, CustomProperties = CaptureCustomProperties(obj) }; } try { // Preparar objetos para serialización foreach (var obj in pageState.LocalObjects) { obj.SalvarDatosNoSerializables(); } var json = JsonConvert.SerializeObject(pageState, GetSerializerSettings()); await File.WriteAllTextAsync(path, json); _hasUnsavedChanges = false; PageStateChanged?.Invoke(this, pageId); } finally { // Restaurar estado de objetos foreach (var obj in pageState.LocalObjects) { obj.RestaurarDatosNoSerializables(); } } } private Dictionary<string, object> CaptureCustomProperties(osBase obj) { var customProps = new Dictionary<string, object>(); var properties = obj.GetType().GetProperties() .Where(p => p.GetCustomAttributes(typeof(SerializeAttribute), true).Any()); foreach (var prop in properties) { customProps[prop.Name] = prop.GetValue(obj); } return customProps; } public async Task SaveAllAsync() { foreach (var pageId in _loadedPages.Keys) { await SavePageStateAsync(pageId); } await SaveGlobalStateAsync(); } public void Dispose() { if (_hasUnsavedChanges) { SaveAllAsync().Wait(); } } } /// <summary> /// Atributo para marcar propiedades que deben ser serializadas como propiedades personalizadas /// </summary> [AttributeUsage(AttributeTargets.Property)] public class SerializeAttribute : Attribute { } }