using System.ComponentModel;
using Evos.Api.Common.Families;
using Evos.Api.Model;
using Evos.Api.Model.Graphic;
using Evos.Api.Model.PredefinedFamilies;
using Evos.Api.Samples.Infrastructure;
using Evos.CommonValueObjects;
using Nanosoft.Collections.WriteProtected.ReadOnly.List;
using Nanosoft.Common.InternationalSystemOfUnits.Access;
using Nanosoft.Common.ValueObjects.Common;

namespace Evos.Api.Samples
{
    /// Сборка обменной модели: продукты, экземпляры, системы, линейные продукты и графика.
    /// Это отправная точка для всех остальных сэмплов — модель здесь только создаётся в памяти.
    [Localizable(false)]
    public static class BuildExchangeModelSample
    {
        public static void Run()
        {
            var projectId = Guid.NewGuid();

            ShowModelSummary("каталог", CreateCatalogModel(projectId));
            ShowModelSummary("сборка с экземплярами", CreateModelWithInstances(projectId));
            ShowModelSummary("система и линейный продукт", CreateModelWithSystemAndLinearProduct(projectId));
        }

        /// Две позиции каталога: ключевые свойства задают тождество продукта,
        /// обычные свойства — его атрибуты.
        public static IReadOnlyExchangeModel CreateCatalogModel(Guid projectId)
        {
            var builder = ExchangeModelBuilderFactory.Create(projectId);

            var pump = builder.NewProduct();
            // Ключевые свойства — то, по чему Evos отличает один продукт от другого.
            // Два продукта с одинаковым набором ключевых свойств считаются дубликатами.
            pump.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Grundfos");
            pump.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "UPS-25");
            pump.Properties.SetValue(EvosFamilies.ProductName.Name, "Насос циркуляционный");

            var valve = builder.NewProduct();
            valve.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Danfoss");
            valve.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "RA-N15");
            valve.Properties.SetValue(EvosFamilies.ProductName.Name, "Клапан радиаторный");

            // Build() возвращает OperationResult: ValueOrThrow() отдаёт модель либо падает,
            // выписав в исключение все диагностики сборки.
            return builder.Build().ValueOrThrow();
        }

        /// Продукт-сборка с тремя экземплярами продукта-детали, расставленными матрицами.
        public static IReadOnlyExchangeModel CreateModelWithInstances(Guid projectId)
        {
            var builder = ExchangeModelBuilderFactory.Create(projectId);

            var assembly = builder.NewProduct();
            assembly.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            assembly.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "ASSEMBLY-1");
            assembly.Properties.SetValue(EvosFamilies.ProductName.Name, "Сборка");

            var part = builder.NewProduct();
            part.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            part.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "PART-1");
            part.Properties.SetValue(EvosFamilies.ProductName.Name, "Деталь");

            // Меш кладётся на продукт-деталь, экземпляры переиспользуют его геометрию.
            builder.GraphicsBuilder.SetProductMesh(CreateTriangle(Color.FromSRgb(0, 128, 255, 255, false)), part);

            for (var i = 0; i < 3; i++)
            {
                var instance = assembly.NewProductInstance(part);

                // Экземпляры одного и того же продукта различаются ключевыми свойствами.
                // Без своего идентификатора три экземпляра детали — дубликаты, и Build() их отвергнет:
                // имя и матрица лежат в обычных свойствах и в тождество не входят.
                instance.KeyProperties.SetValue(GraphicFamilies.ModelObjectId.ObjectId, Guid.NewGuid());

                instance.Properties.SetValue(EvosFamilies.ProductName.Name, $"Деталь {i + 1}");
                instance.Properties.SetValue(
                    EvosFamilies.SpaceGraphicModel.Transformation,
                    CreateTranslation(i * 2.0, 0, 0));
            }

            return builder.Build().ValueOrThrow();
        }

        /// Система с именем и цветом подсветки, линейный продукт заданной длины и привязка одного
        /// к другому. Система живёт на экземпляре, а не на продукте: к системе относится не тип
        /// изделия, а конкретное его вхождение в проект.
        private static IReadOnlyExchangeModel CreateModelWithSystemAndLinearProduct(Guid projectId)
        {
            var builder = ExchangeModelBuilderFactory.Create(projectId);

            var system = builder.NewSystem("П1 Приточная", Color.FromSRgb(0, 200, 0, 255, false));

            // Линейному продукту нужен графический продукт — его сегмент, который тиражируется по длине.
            var segment = builder.GraphicsBuilder.GetOrCreateGraphicProduct(CreateTriangle(Color.FromSRgb(200, 200, 200, 255, false)));

            var duct = builder.NewLinearProduct(segment, CreateLength(3.5));
            duct.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            duct.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "DUCT-100");
            duct.Properties.SetValue(EvosFamilies.ProductName.Name, "Воздуховод");

            // Воздуховод-тип попадает в проект экземпляром внутри сборки — и вот он уже в системе.
            var section = builder.NewProduct();
            section.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            section.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "SECTION-1");
            section.Properties.SetValue(EvosFamilies.ProductName.Name, "Участок П1");

            var ductInstance = section.NewProductInstance(duct);
            ductInstance.KeyProperties.SetValue(GraphicFamilies.ModelObjectId.ObjectId, Guid.NewGuid());
            ductInstance.Properties.SetValue(EvosFamilies.ProductName.Name, "Воздуховод 1");
            ductInstance.System = system;

            return builder.Build().ValueOrThrow();
        }

        private static void ShowModelSummary(string modelName, IReadOnlyExchangeModel model)
        {
            Console.WriteLine($"Собрана модель «{modelName}»: продуктов — {model.Products.Count}, семейств — {model.FamilyDescriptions.Count}");
        }

        private static MeshGraphic CreateTriangle(Color color)
        {
            var vertices = new List<Position3D>
            {
                CreatePosition(-0.5, 0, 0),
                CreatePosition(0, 1, 0),
                CreatePosition(0.5, 0, 0)
            }.ToReadOnly();

            return new MeshGraphic(vertices, new List<int> { 0, 1, 2 }.ToReadOnly(), color);
        }

        private static TransformationMatrix CreateTranslation(double x, double y, double z)
        {
            return new TransformationMatrix(
                CreatePosition(x, y, z),
                new Vector3(1, 0, 0),
                new Vector3(0, 1, 0),
                new Vector3(0, 0, 1));
        }

        private static Position3D CreatePosition(double x, double y, double z)
        {
            return new Position3D(
                ValueObjectsAccess.Meter.FromMeter(x),
                ValueObjectsAccess.Meter.FromMeter(y),
                ValueObjectsAccess.Meter.FromMeter(z));
        }

        private static Length CreateLength(double meters)
        {
            Length.TryCreate(ValueObjectsAccess.Meter.FromMeter(meters), out var length);
            SampleCheck.That(length is not null, "длина должна создаваться из положительного значения");
            return length!;
        }
    }
}
