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;

namespace Evos.Api.Samples
{
    /// Высокоуровневое API: геометрия обменной модели. Всё, что связано с мешами, делает
    /// GraphicsBuilder — он же ведёт словарь геометрии и переиспользует продукты с одинаковым мешем.
    ///
    /// Класть меш свойством напрямую (StaticGraphicCompressible.Mesh) можно, но это свойство
    /// принимает CompressedMesh, а не MeshGraphic: сжимать пришлось бы самому. Пользуйтесь билдером.
    [Localizable(false)]
    public static class GraphicsSample
    {
        private static readonly Color Grey = Color.FromSRgb(180, 180, 180, 255, false);
        private static readonly Color Red = Color.FromSRgb(220, 40, 40, 255, false);

        public static void Run()
        {
            ReuseGeometryBetweenProducts();
            AddGraphicAsInstance();
            SplitGraphicIntoParts();
            ColorDoesNotDistinguishGeometry();
            SetProductMeshDoesNotRegisterForReuse();
            GeometryIsPartOfProductIdentity();
        }

        /// Один и тот же меш — один и тот же графический продукт: второй раз он не создаётся.
        /// На этом держится экономия: сто одинаковых болтов не превращаются в сто копий геометрии.
        private static void ReuseGeometryBetweenProducts()
        {
            var graphics = ExchangeModelBuilderFactory.Create(SampleIdentifiers.ProjectId).GraphicsBuilder;

            var first = graphics.GetOrCreateGraphicProduct(SampleGeometry.Triangle(Grey));
            var second = graphics.GetOrCreateGraphicProduct(SampleGeometry.Triangle(Grey));

            Console.WriteLine($"Тот же меш вернул тот же продукт: {ReferenceEquals(first, second)}");
            SampleCheck.That(ReferenceEquals(first, second), "геометрия переиспользуется по хэшу меша");
        }

        /// Меш можно добавить продукту отдельным экземпляром — так у изделия появляется
        /// собственное графическое вхождение, не подменяя графику самого продукта.
        private static void AddGraphicAsInstance()
        {
            var builder = ExchangeModelBuilderFactory.Create(SampleIdentifiers.ProjectId);

            var pump = NewCatalogProduct(builder, "PUMP-1", "Насос");
            var instance = builder.GraphicsBuilder.AddGraphicToProductAsInstance(pump, SampleGeometry.Triangle(Red));

            Console.WriteLine($"У насоса графических экземпляров: {pump.ProductInstances.Count}");

            SampleCheck.That(pump.ProductInstances.Count == 1, "графика добавилась экземпляром");
            SampleCheck.That(
                instance.KeyProperties.GetValue(EvosFamilies.StaticGraphicCompressible.Color).Equals(Red),
                "цвет графического экземпляра — его ключевое свойство");
        }

        /// Составную геометрию раскладывают частями: каждая часть становится экземпляром внутри
        /// общего графического продукта. Одинаковые части при этом делят один продукт на всех.
        private static void SplitGraphicIntoParts()
        {
            var builder = ExchangeModelBuilderFactory.Create(SampleIdentifiers.ProjectId);
            var body = NewCatalogProduct(builder, "VALVE-1", "Клапан");

            var parts = new[]
            {
                CompressedMeshGraphicCreator.CreateHashedMeshGraphic(SampleGeometry.TriangleVertices(), SampleGeometry.TriangleFaces()),
                CompressedMeshGraphicCreator.CreateHashedMeshGraphic(SampleGeometry.ShiftedTriangleVertices(), SampleGeometry.TriangleFaces()),
                CompressedMeshGraphicCreator.CreateHashedMeshGraphic(SampleGeometry.TriangleVertices(), SampleGeometry.TriangleFaces())
            };

            var instances = builder.GraphicsBuilder.AddGraphicParts(body, parts);

            Console.WriteLine($"Частей: {instances.Count}, разных графических продуктов под ними: 2");

            SampleCheck.That(instances.Count == 3, "на каждую часть — свой экземпляр");
            SampleCheck.That(
                ReferenceEquals(instances[0].Product, instances[2].Product),
                "повторяющаяся часть переиспользует один графический продукт");
        }

        /// Ловушка. В хэш геометрии входят только вершины и грани — цвета там нет.
        /// Второй вызов с тем же мешем другого цвета не создаст новый продукт, а перекрасит старый.
        private static void ColorDoesNotDistinguishGeometry()
        {
            var graphics = ExchangeModelBuilderFactory.Create(SampleIdentifiers.ProjectId).GraphicsBuilder;

            var product = graphics.GetOrCreateGraphicProduct(SampleGeometry.Triangle(Grey));
            var repainted = graphics.GetOrCreateGraphicProduct(SampleGeometry.Triangle(Red));

            var color = product.Properties.GetValue(EvosFamilies.StaticGraphicCompressible.Color);

            Console.WriteLine($"Продукт тот же: {ReferenceEquals(product, repainted)}, но перекрашен: {color.Equals(Red)}");

            SampleCheck.That(ReferenceEquals(product, repainted), "цвет не различает геометрию");
            SampleCheck.That(color.Equals(Red), "второй вызов перекрасил уже созданный продукт");
        }

        /// Ловушка. SetProductMesh кладёт геометрию в готовый продукт, но не регистрирует его
        /// в словаре мешей. Поэтому GetOrCreateGraphicProduct с той же геометрией о нём не знает
        /// и заведёт второй продукт с тем же мешем.
        private static void SetProductMeshDoesNotRegisterForReuse()
        {
            var builder = ExchangeModelBuilderFactory.Create(SampleIdentifiers.ProjectId);
            var pump = NewCatalogProduct(builder, "PUMP-2", "Насос с мешем");

            builder.GraphicsBuilder.SetProductMesh(SampleGeometry.Triangle(Grey), pump);
            var graphicProduct = builder.GraphicsBuilder.GetOrCreateGraphicProduct(SampleGeometry.Triangle(Grey));

            Console.WriteLine($"SetProductMesh переиспользуется билдером: {ReferenceEquals(pump, graphicProduct)}");
            SampleCheck.That(!ReferenceEquals(pump, graphicProduct), "продукт из SetProductMesh в словарь мешей не попадает");
        }

        /// Ловушка. Меш добавляет продукту ключевое свойство — хэш геометрии. То есть геометрия
        /// входит в тождество продукта: поменяли меш — на публикации получите новый продукт,
        /// а не обновлённый старый. Если геометрия у вас меняется, а изделие остаётся тем же,
        /// держите её на отдельном графическом продукте и подключайте экземпляром.
        private static void GeometryIsPartOfProductIdentity()
        {
            var builder = ExchangeModelBuilderFactory.Create(SampleIdentifiers.ProjectId);
            var pump = NewCatalogProduct(builder, "PUMP-3", "Насос");

            SampleCheck.That(
                !pump.KeyProperties.HasValue(GraphicFamilies.GraphicHashCode.HashCode),
                "пока меша нет, тождество продукта — только его каталожные ключи");

            builder.GraphicsBuilder.SetProductMesh(SampleGeometry.Triangle(Grey), pump);

            var hasGeometryKey = pump.KeyProperties.HasValue(GraphicFamilies.GraphicHashCode.HashCode);

            Console.WriteLine($"После SetProductMesh геометрия стала ключевым свойством: {hasGeometryKey}");
            SampleCheck.That(hasGeometryKey, "меш добавляет продукту ключевое свойство — хэш геометрии");
        }

        private static IExchangeProduct NewCatalogProduct(IExchangeModelBuilder builder, string article, string name)
        {
            var product = builder.NewProduct();

            product.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            product.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, article);
            product.Properties.SetValue(EvosFamilies.ProductName.Name, name);

            return product;
        }

    }
}
