using System.ComponentModel;
using Evos.Api.Repository;
using Evos.Api.Samples.Infrastructure;
using Evos.ModelsRepository.BasicData;
using Evos.ModelsRepository.Connection;
using Evos.ModelsRepository.DataProvider.TransactionDescriptions;
using Evos.ModelsRepository.Products;
using Evos.CommonValueObjects;
using Evos.ModelsRepository.SpaceModel;
using Evos.Repeater.GraphicCutting;
using Nanosoft.Common.InternationalSystemOfUnits;
using Nanosoft.Common.InternationalSystemOfUnits.Access;
using Nanosoft.Common.ValueObjects.Common;

namespace Evos.Api.Samples
{
    /// Низкоуровневое API: повторитель (repeater) — продукт, который тиражирует геометрию
    /// другого продукта вдоль оси с заданным шагом. Так строят воздуховоды, лотки, трубы.
    [Localizable(false)]
    public static class RepeaterSample
    {
        private const double SegmentLengthInMeters = 0.5;
        private const double AxisLengthInMeters = 2.3;

        // Ось 2.3 м при шаге 0.5 м — это четыре целых сегмента и пятый, которому места хватило
        // только на 0.3 м. Числа выбраны так, чтобы последний сегмент пришлось подрезать.
        private const int ExpectedSegmentsCount = 5;
        private const double ExpectedLastCutInMeters = 0.2;

        public static void Run()
        {
            using var sampleRepository = SampleRepository.CreateNew();

            sampleRepository.Repository.TryExecuteTransaction(
                RepeatSegmentAlongAxis,
                new TransactionInfo(TransactionDescription.Default, TransactionMode.Default));
        }

        /// Повторитель создаётся только внутри write-транзакции: он добавляет в репозиторий
        /// новый продукт-повторитель, ссылающийся на тиражируемый сегмент.
        private static void RepeatSegmentAlongAxis(RepositoryWriteAccess writeAccess)
        {
            var segment = CreateRepetitiveSegment(writeAccess);
            var step = Length.CreateOrThrow(ValueObjectsAccess.Meter.FromMeter(SegmentLengthInMeters));

            var repeaterFactory = new ApiRepeaterFactory();

            // FindOrCreateRepeater ищет повторитель по сегменту: если у сегмента он уже есть —
            // возвращается существующий, второй не создаётся. Флаг говорит, что именно произошло.
            var repeater = repeaterFactory.FindOrCreateRepeater(
                writeAccess,
                segment,
                step,
                new MeshCuttingAdditionalSettings(),
                out var created);

            SampleCheck.That(created, "первый вызов должен создать повторитель");
            Console.WriteLine($"Повторитель создан, шаг {step}");

            var sameRepeater = repeaterFactory.FindOrCreateRepeater(
                writeAccess,
                segment,
                step,
                new MeshCuttingAdditionalSettings(),
                out var createdAgain);

            SampleCheck.That(!createdAgain, "повторный вызов не должен создавать второй повторитель");
            SampleCheck.That(ReferenceEquals(sameRepeater, repeater), "должен вернуться тот же повторитель");

            var repeaters = repeaterFactory.FindRepeaters(writeAccess, segment).ToList();
            Console.WriteLine($"Повторителей у сегмента: {repeaters.Count}");
            SampleCheck.That(repeaters.Count == 1, "у сегмента должен быть ровно один повторитель");

            ShowStepIsIgnoredForExistingRepeater(writeAccess, repeaterFactory, segment, repeater, step);

            PlaceRepeaterOnAxis(writeAccess, repeater);
        }

        /// Ловушка. Шаг участвует только в создании: при поиске он не учитывается вовсе.
        /// Позвали FindOrCreateRepeater с другим шагом — получите существующий повторитель
        /// со старым шагом, и никто об этом не скажет. Менять шаг надо самому, через семейство
        /// IRepeatAlongAxisParameters; а нужен второй повторитель — есть CreateRepeater.
        private static void ShowStepIsIgnoredForExistingRepeater(
            RepositoryWriteAccess writeAccess,
            ApiRepeaterFactory repeaterFactory,
            IProduct segment,
            IProduct existingRepeater,
            Length originalStep)
        {
            var otherStep = Length.CreateOrThrow(ValueObjectsAccess.Meter.FromMeter(SegmentLengthInMeters * 2));

            var repeater = repeaterFactory.FindOrCreateRepeater(
                writeAccess,
                segment,
                otherStep,
                new MeshCuttingAdditionalSettings(),
                out var created);

            var actualStep = repeater.GetPredefinedFamilyOrThrow<IRepeatAlongAxisParameters>()
                .Facade()
                .DistanceBetweenElements;

            Console.WriteLine($"Запросили шаг {otherStep}, у повторителя остался {actualStep}");

            SampleCheck.That(!created, "повторитель ищется по сегменту, шаг в поиске не участвует");
            SampleCheck.That(ReferenceEquals(repeater, existingRepeater), "вернулся тот же повторитель");
            SampleCheck.That(actualStep.Equals(originalStep), "новый шаг молча проигнорирован");
        }

        /// Сам по себе повторитель — заготовка: продукт с осью, но без места в модели.
        /// Сегменты появляются, когда повторитель вставили экземпляром в другой продукт
        /// и задали ось — она и решает, сколько сегментов встанет и где.
        private static void PlaceRepeaterOnAxis(RepositoryWriteAccess writeAccess, IProduct repeater)
        {
            var trace = writeAccess.NewNamedProduct("Трасса", ProductKind.Designed);
            var repeaterInstance = trace.NewProductInstance(repeater);

            repeaterInstance.GetPredefinedFamilyOrThrow<IAxis>()
                .EditableFacade()
                .SetProperty(static axis => axis.InterpolationPoints, CreateAxisPoints());

            // Экземпляры сегментов репозиторий разворачивает сам: их количество — формула от длины оси
            // и шага, пересчитывается на лету. Отдельно создавать их не нужно и нельзя.
            var segments = repeaterInstance.NestedInstances.ToList();

            Console.WriteLine($"Сегментов на оси {AxisLengthInMeters} м: {segments.Count}");
            SampleCheck.That(
                segments.Count == ExpectedSegmentsCount,
                $"на оси {AxisLengthInMeters} м с шагом {SegmentLengthInMeters} м должно встать {ExpectedSegmentsCount} сегментов");

            AssertLastSegmentIsCut(segments);
        }

        /// Подрезка последнего сегмента — это не правка меша, а свойство CutLength: сколько отрезать
        /// с конца при отрисовке. Целым сегментам резать нечего, последнему — ровно то, что не влезло.
        private static void AssertLastSegmentIsCut(IReadOnlyList<IProductInstance> segments)
        {
            var expectedLastCut = ValueObjectsAccess.Meter.FromMeter(ExpectedLastCutInMeters);

            for (var index = 0; index < segments.Count; index++)
            {
                var cutLength = segments[index]
                    .GetPredefinedFamilyOrThrow<IStaticGraphic>()
                    .Facade()
                    .CutLength;

                var isLast = index + 1 == segments.Count;
                if (!isLast)
                {
                    SampleCheck.That(
                        cutLength.Equals(Distance.Zero),
                        $"целый сегмент {index} не должен быть подрезан");
                    continue;
                }

                Console.WriteLine($"Последний сегмент подрезан на {cutLength}");
                SampleCheck.That(
                    cutLength.Meter.Equals(expectedLastCut, Meter.Epsilon),
                    $"последний сегмент должен быть подрезан на {ExpectedLastCutInMeters} м");
            }
        }

        /// Ось задаётся ломаной; здесь — отрезок вдоль Oz, вдоль него и повторяются сегменты.
        private static IReadOnlyList<Position3D> CreateAxisPoints()
        {
            return new List<Position3D>
            {
                SampleGeometry.Position(0, 0, 0),
                SampleGeometry.Position(0, 0, AxisLengthInMeters)
            };
        }

        /// Тиражируемый сегмент — обычный продукт, которому нужны два предопределённых семейства:
        /// геометрия (IStaticGraphic) и её положение в пространстве (ISpaceGraphicModel).
        /// Без них повторитель строить не из чего, и фабрика бросит исключение.
        /// Семейство добавляют пустым, значения кладут через EditableFacade — и только в транзакции.
        private static IProduct CreateRepetitiveSegment(RepositoryWriteAccess writeAccess)
        {
            var segment = writeAccess.NewProduct();

            var graphic = segment.AddFamilyOrThrow<IStaticGraphic>();
            segment.AddFamilyOrThrow<ISpaceGraphicModel>();

            // Геометрия сегмента — треугольные грани: вершины плюс индексы, ссылающиеся на них.
            graphic.EditableFacade()
                .SetProperty(family => family.Vertices, CreateBoxVertices())
                .SetProperty(family => family.Faces, CreateBoxFaces())
                .SetProperty(family => family.Color, Color.FromSRgb(160, 160, 170, 255, false));

            return segment;
        }

        /// Восемь вершин прямоугольного бруска 0.4 × 0.4 м — заготовка воздуховода.
        /// Длина отложена вдоль Oz: повторяют сегмент вдоль той же оси, и брусок должен лежать по ней,
        /// иначе сегменты встанут поперёк трассы.
        private static IReadOnlyList<Position3D> CreateBoxVertices()
        {
            const double half = 0.2;
            const double length = SegmentLengthInMeters;

            return new List<Position3D>
            {
                SampleGeometry.Position(-half, -half, 0),
                SampleGeometry.Position(half, -half, 0),
                SampleGeometry.Position(half, half, 0),
                SampleGeometry.Position(-half, half, 0),
                SampleGeometry.Position(-half, -half, length),
                SampleGeometry.Position(half, -half, length),
                SampleGeometry.Position(half, half, length),
                SampleGeometry.Position(-half, half, length)
            };
        }

        /// Двенадцать треугольников: по два на каждую из шести граней бруска.
        private static IReadOnlyList<int> CreateBoxFaces()
        {
            return new List<int>
            {
                0, 1, 2, 0, 2, 3,
                4, 6, 5, 4, 7, 6,
                0, 4, 5, 0, 5, 1,
                1, 5, 6, 1, 6, 2,
                2, 6, 7, 2, 7, 3,
                3, 7, 4, 3, 4, 0
            };
        }

    }
}
