using System.ComponentModel;
using Evos.Api.Samples.Infrastructure;
using Evos.ModelsRepository;
using Evos.ModelsRepository.BasicData;
using Evos.ModelsRepository.BasicData.Levels;
using Evos.ModelsRepository.DataProvider.TransactionDescriptions;
using Evos.ModelsRepository.Products;
using Evos.ModelsRepository.SpaceModel;

namespace Evos.Api.Samples
{
    /// Низкоуровневое API: ежедневная работа со свойствами — прочитать, записать, переопределить
    /// в экземпляре, пометить экземпляр присоединяемым семейством.
    ///
    /// Свойства живут в семействах и доступны через фасад: Facade() — чтение, EditableFacade() —
    /// запись. Запись — всегда внутри транзакции.
    [Localizable(false)]
    public static class RepositoryEditSample
    {
        public static void Run()
        {
            using var sampleRepository = SampleRepository.CreateNew();
            var repository = sampleRepository.Repository;

            ReadAndWriteProperty(repository);
            OverridePropertyInInstance(repository);
            AttachInstanceToLevel(repository);
        }

        /// Чтение и запись одного свойства. GetPredefinedFamilyOrThrow бросает, если семейства нет;
        /// когда не уверены — TryGetPredefinedFamily.
        private static void ReadAndWriteProperty(IConcurrentRepository repository)
        {
            repository.TryExecuteTransaction(
                writeAccess =>
                {
                    var pump = writeAccess.NewNamedProduct("Насос", ProductKind.Designed);

                    pump.GetPredefinedFamilyOrThrow<IProductName>()
                        .EditableFacade()
                        .SetProperty(family => family.Name, "Насос циркуляционный");
                },
                new TransactionInfo(TransactionDescription.AddingProduct, TransactionMode.Default));

            using var connection = repository.ReadConnection();

            var name = connection.GetProducts()
                .Single()
                .GetPredefinedFamilyOrThrow<IProductName>()
                .Facade()
                .Name;

            Console.WriteLine($"Имя продукта после правки: {name}");
            SampleCheck.That(name == "Насос циркуляционный", "имя должно записаться");
        }

        /// Продукт — это тип: значение, заданное продукту, действует на все его экземпляры сразу.
        /// Чтобы одна лампа из двух отличалась, свойство сначала открывают (SetUnlock), и только
        /// потом переопределяют на экземпляре. Закрытое свойство одинаково у всех экземпляров.
        private static void OverridePropertyInInstance(IConcurrentRepository repository)
        {
            repository.TryExecuteTransaction(
                writeAccess =>
                {
                    var lamp = writeAccess.NewNamedProduct("Лампа", ProductKind.Designed);
                    var chandelier = writeAccess.NewNamedProduct("Люстра", ProductKind.Designed);

                    var first = chandelier.NewProductInstance(lamp);
                    var second = chandelier.NewProductInstance(lamp);

                    // Пока имя закрыто, оба экземпляра видят значение продукта.
                    SampleCheck.That(
                        ReadInstanceName(first) == "Лампа" && ReadInstanceName(second) == "Лампа",
                        "экземпляры наследуют значение продукта");

                    lamp.GetPredefinedFamilyOrThrow<IProductName>()
                        .EditableFacade()
                        .SetUnlock(family => family.Name, true);

                    second.GetPredefinedFamilyOrThrow<IProductName>()
                        .EditableFacade()
                        .SetProperty(family => family.Name, "Лампа аварийная");

                    Console.WriteLine($"Лампы в люстре: {ReadInstanceName(first)}, {ReadInstanceName(second)}");

                    SampleCheck.That(ReadInstanceName(first) == "Лампа", "первый экземпляр не трогали");
                    SampleCheck.That(ReadInstanceName(second) == "Лампа аварийная", "второй экземпляр переопределён");
                },
                new TransactionInfo(TransactionDescription.Default, TransactionMode.Default));
        }

        /// Присоединяемые семейства помечают вхождение, а не тип: на уровне стоит не «насос вообще»,
        /// а конкретный экземпляр насоса. Так экземпляр привязывают к уровню.
        ///
        /// Обратите внимание, чем адресуется уровень: не продуктом и не экземпляром, а
        /// идентификатором семейства ILevel на экземпляре уровня.
        private static void AttachInstanceToLevel(IConcurrentRepository repository)
        {
            repository.TryExecuteTransaction(
                writeAccess =>
                {
                    var levelProduct = writeAccess.NewNamedProduct("Уровень", ProductKind.Designed);
                    levelProduct.AddFamilyOrThrow<ILevel>();

                    var pumpProduct = writeAccess.NewNamedProduct("Насос К1", ProductKind.Designed);
                    var building = writeAccess.NewNamedProduct("Здание", ProductKind.Designed);

                    var levelInstance = building.NewProductInstance(levelProduct);
                    var pumpInstance = building.NewProductInstance(pumpProduct);

                    var levelId = levelInstance.GetPredefinedFamilyOrThrow<ILevel>().Facade().FamilyId.AsUntyped();

                    pumpInstance.GetOrAttachTransientFamily<IProductCurrentLevel>()
                        .AsEditable()
                        .SetProperty(family => family.LevelId, levelId);

                    var attached = pumpInstance.GetOrAttachTransientFamily<IProductCurrentLevel>().Facade().LevelId;

                    Console.WriteLine($"Насос привязан к уровню: {attached.Equals(levelId)}");
                    SampleCheck.That(attached.Equals(levelId), "экземпляр насоса должен стоять на уровне");
                },
                new TransactionInfo(TransactionDescription.Default, TransactionMode.Default));
        }

        private static string ReadInstanceName(IProductInstance instance)
        {
            return instance.GetPredefinedFamilyOrThrow<IProductName>().Facade().Name;
        }
    }
}
