﻿namespace HilditchThinning
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using DistanceTransformation;
    using Graph;
    using Regions;

    public class HilditchThinning// : IHilditchThinning
    {
        private static readonly Node OutOfBoundaryNode = new Node(-1, -1, false);

        public bool[,] Process(bool[,] shape, Bitmap bitmap)
        {
            var width = shape.GetLength(0);
            var height = shape.GetLength(1);

            shape = Preprocessing.RemoveTinyIsolatedObstacles(shape);
            var clearance = DistanceTransform.Process(shape);

            var graph = GridGraph.Convert(shape, NeighborsMode.EightNeighbors);

            const int minClearance = 3;
            graph.Where(node => clearance[node.X, node.Y].AllDirections.Min() <= minClearance).ForEach(node => node.BelongsToShape = false);
            var skeleton = Skeletonization.Process(graph).ToList();

            VertexEdgeGraph.SetConnectingNeighbors(skeleton);
            var connectedGraph = VertexEdgeGraph.ConnectVertices(skeleton).ToList();

            const int pruneDistance = 15;
            var prunedSkeleton = Pruning.Prune(connectedGraph, clearance, pruneDistance).ToList();

            var chokeBorders = ChokePointsDetector.GetChokeBorders(prunedSkeleton, tile => IsWallOrOutOfBounds(tile, shape));
            var tileIsChokeMap = Dictionary.CreateTileIsChokeMap(width, height, chokeBorders);
            var graphFourNeighbors = GridGraph.Convert(shape, NeighborsMode.FourNeighbors).ToList();
            SetChokeTilesAsWall(graphFourNeighbors, tileIsChokeMap);
            var tileNodeMap = Dictionary.CreateTileNodeMap(width, height, graphFourNeighbors);
            var allRegions = RegionAnalyzer.FindRegions(graphFourNeighbors, tileNodeMap, (x, y) => tileNodeMap[x, y].BelongsToShape).ToList();
            var pointRegionMap = Dictionary.CreateNodeRegionMap(width, height, allRegions);
            var chokeRegionMap = Dictionary.CreateChokeRegionMap(chokeBorders, pointRegionMap);
            var regionChokeMap = Dictionary.CreateRegionChokeMap(chokeRegionMap);
            var chokes = ChokePointsDetector.CreateFilledChokeRegions(chokeBorders, chokeRegionMap, regionChokeMap, tileNodeMap);

            var allChokeNodes = chokes.SelectMany(ch => ch.Fill).ToHashSet();
            var nonChokeRegions = allRegions.Where(r => !allChokeNodes.Contains(r.Nodes.First()));

            var regionEdges = nonChokeRegions.Select(RegionEdge).Select(edgeNodes => new Region<Node>(edgeNodes.ToHashSet()));

            DebugUtils.FillIntoBitmap(chokes, bitmap, Color.Red);
            DebugUtils.FillIntoBitmap(regionEdges, bitmap);

            return null;
        }

        public static bool IsWallOrOutOfBounds((int X, int Y) tile, bool[,] map)
        {
            var width = map.GetLength(0);
            var height = map.GetLength(1);
            if (tile.X < 0 || tile.X >= width) return true;
            if (tile.Y < 0 || tile.Y >= height) return true;

            return !map[tile.X, tile.Y];
        }

        private static void SetChokeTilesAsWall(IEnumerable<Node> nodes, bool[,] tileIsChokeMap) =>
            nodes.Where(n => tileIsChokeMap[n.X, n.Y]).ForEach(n => n.BelongsToShape = false);

        private static IEnumerable<Node> RegionEdge(Region<Node> region) => region.Nodes.Where(n => n.Neighbors.Any(nn => !nn.BelongsToShape));
    }
}