﻿// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using HoloToolkit.Unity;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace HoloToolkit.Examples.SpatialUnderstandingFeatureOverview
{
    public class SpaceVisualizer : LineDrawer
    {
        // Singleton
        public static SpaceVisualizer Instance;

        // Consts
        const int QueryResultMaxCount = 512;
        const int DisplayResultMaxCount = 32;

        // Privates
        private List<AnimatedBox> lineBoxList = new List<AnimatedBox>();
        private SpatialUnderstandingDllTopology.TopologyResult[] resultsTopology = new SpatialUnderstandingDllTopology.TopologyResult[QueryResultMaxCount];
        private SpatialUnderstandingDllShapes.ShapeResult[] resultsShape = new SpatialUnderstandingDllShapes.ShapeResult[QueryResultMaxCount];

        // Functions
        private void Awake()
        {
            Instance = this;
        }

        public void ClearGeometry(bool clearAll = true)
        {
            lineBoxList = new List<AnimatedBox>();
            AppState.Instance.SpaceQueryDescription = "";

            if (clearAll && (LevelSolver.Instance != null))
            {
                LevelSolver.Instance.ClearGeometry(false);
            }
        }

        public void Query_PlayspaceAlignment()
        {
            // First clear all our geo
            ClearGeometry();

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Alignment information
            SpatialUnderstandingDll.Imports.QueryPlayspaceAlignment(SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignmentPtr());
            SpatialUnderstandingDll.Imports.PlayspaceAlignment alignment = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignment();

            // Box for the space
            float timeDelay = (float)lineBoxList.Count * AnimatedBox.DelayPerItem;
            lineBoxList.Add(
                new AnimatedBox(
                    timeDelay,
                    new Vector3(alignment.Center.x, (alignment.CeilingYValue + alignment.FloorYValue) * 0.5f, alignment.Center.z),
                    Quaternion.LookRotation(alignment.BasisZ, alignment.BasisY),
                    Color.magenta,
                    new Vector3(alignment.HalfDims.x, (alignment.CeilingYValue - alignment.FloorYValue) * 0.5f, alignment.HalfDims.z))
            );
            AppState.Instance.SpaceQueryDescription = "Playspace Alignment OBB";
        }

        public void Query_Topology_FindPositionOnWall()
        {
            ClearGeometry();

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Setup
            float minHeightOfWallSpace = 0.5f;
            float minWidthOfWallSpace = 0.75f;
            float minHeightAboveFloor = 1.25f;
            float minFacingClearance = 1.5f;

            // Query
            IntPtr resultsTopologyPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
            int locationCount = SpatialUnderstandingDllTopology.QueryTopology_FindPositionsOnWalls(
                minHeightOfWallSpace, minWidthOfWallSpace, minHeightAboveFloor, minFacingClearance,
                resultsTopology.Length, resultsTopologyPtr);

            // Output
            HandleResults_Topology("Find Position On Wall", locationCount, new Vector3(minWidthOfWallSpace, minHeightOfWallSpace, 0.025f), Color.blue);
        }

        public void Query_Topology_FindLargePositionsOnWalls()
        {
            // Setup
            float minHeightOfWallSpace = 1.0f;
            float minWidthOfWallSpace = 1.5f;
            float minHeightAboveFloor = 1.5f;
            float minFacingClearance = 0.5f;

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Query
            IntPtr resultsTopologyPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
            int locationCount = SpatialUnderstandingDllTopology.QueryTopology_FindLargePositionsOnWalls(
                minHeightOfWallSpace, minWidthOfWallSpace, minHeightAboveFloor, minFacingClearance,
                resultsTopology.Length, resultsTopologyPtr);

            // Output
            HandleResults_Topology("Find Large Positions On Walls", locationCount, new Vector3(minWidthOfWallSpace, minHeightOfWallSpace, 0.025f), Color.yellow);
        }

        public void Query_Topology_FindLargeWall()
        {
            ClearGeometry();

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Query
            IntPtr wallPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
            int wallCount = SpatialUnderstandingDllTopology.QueryTopology_FindLargestWall(
                wallPtr);
            if (wallCount == 0)
            {
                AppState.Instance.SpaceQueryDescription = "Find Largest Wall (0)";
                return;
            }

            // Add the line boxes
            float timeDelay = (float)lineBoxList.Count * AnimatedBox.DelayPerItem;
            lineBoxList.Add(
                new AnimatedBox(
                    timeDelay,
                    resultsTopology[0].position,
                    Quaternion.LookRotation(resultsTopology[0].normal, Vector3.up),
                    Color.magenta,
                    new Vector3(resultsTopology[0].width, resultsTopology[0].length, 0.05f) * 0.5f)
            );
            AppState.Instance.SpaceQueryDescription = "Find Largest Wall (1)";
        }

        public void Query_Topology_FindPositionsOnFloor()
        {
            // Setup
            float minWidthOfWallSpace = 1.0f;
            float minHeightAboveFloor = 1.0f;

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Query
            IntPtr resultsTopologyPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
            int locationCount = SpatialUnderstandingDllTopology.QueryTopology_FindPositionsOnFloor(
                minWidthOfWallSpace, minHeightAboveFloor,
                resultsTopology.Length, resultsTopologyPtr);

            // Output
            HandleResults_Topology("Find Positions On Floor", locationCount, new Vector3(minWidthOfWallSpace, 0.025f, minHeightAboveFloor), Color.red);
        }

        public void Query_Topology_FindLargestPositionsOnFloor()
        {
            // Query
            IntPtr resultsTopologyPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
            int locationCount = SpatialUnderstandingDllTopology.QueryTopology_FindLargestPositionsOnFloor(
                resultsTopology.Length, resultsTopologyPtr);

            // Output
            HandleResults_Topology("Find Largest Positions On Floor", locationCount, new Vector3(1.0f, 1.0f, 0.025f), Color.yellow);
        }

        public void Query_Topology_FindPositionsPlaceable()
        {
            // Setup
            float minHeight = 0.125f;
            float maxHeight = 1.0f;
            float minFacingClearance = 1.0f;

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Query
            IntPtr resultsTopologyPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
            int locationCount = SpatialUnderstandingDllTopology.QueryTopology_FindPositionsSittable(
                minHeight, maxHeight, minFacingClearance,
                resultsTopology.Length, resultsTopologyPtr);

            // Output
            HandleResults_Topology("Find Placeable Positions", locationCount, new Vector3(0.25f, 0.025f, 0.25f), Color.cyan);
        }

        public void Query_Topology_FindLargePositionsSittable()
        {
            // Setup
            float minHeight = 0.125f;
            float maxHeight = 2.0f;
            float minFacingClearance = 1.0f;
            float minWidth = .5f;

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
              return;
            }

            // Query
            IntPtr resultsTopologyPtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsTopology);
            int locationCount = SpatialUnderstandingDllTopology.QueryTopology_FindLargePositionsSittable(
                minHeight, maxHeight, minFacingClearance, minWidth,
                resultsTopology.Length, resultsTopologyPtr);

            // Output
            HandleResults_Topology("Find Large Sittable Positions", locationCount, new Vector3(minWidth, 0.025f, minWidth), Color.cyan);
        }

        public void Query_Shape_FindPositionsOnShape(string shapeName)
        {
            // Setup
            float minRadius = 0.1f;

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Query
            IntPtr resultsShapePtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsShape);
            int shapeCount = SpatialUnderstandingDllShapes.QueryShape_FindPositionsOnShape(
                shapeName, minRadius,
                resultsShape.Length, resultsShapePtr);

            // Output
            HandleResults_Shape("Find Positions on Shape '" + shapeName + "'", shapeCount, Color.cyan, new Vector3(0.1f, 0.025f, 0.1f));
        }

        public void Query_Shape_FindShapeHalfDims(string shapeName)
        {
            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Query
            IntPtr resultsShapePtr = SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(resultsShape);
            int shapeCount = SpatialUnderstandingDllShapes.QueryShape_FindShapeHalfDims(
                shapeName,
                resultsShape.Length, resultsShapePtr);

            // Output
            HandleResults_Shape("Find Shape Min/Max '" + shapeName + "'", shapeCount, Color.blue, new Vector3(0.25f, 0.025f, 0.25f));
        }

        private void HandleResults_Topology(string visDesc, int locationCount, Vector3 boxFullDims, Color color)
        {
            // First clear all our geo
            ClearGeometry();

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Add the line boxes (we may have more results than boxes - pick evenly across the results in that case)
            int lineInc = Mathf.CeilToInt((float)locationCount / (float)DisplayResultMaxCount);
            int boxesDisplayed = 0;
            for (int i = 0; i < locationCount; i += lineInc)
            {
                float timeDelay = (float)lineBoxList.Count * AnimatedBox.DelayPerItem;
                lineBoxList.Add(
                    new AnimatedBox(
                        timeDelay,
                        resultsTopology[i].position,
                        Quaternion.LookRotation(resultsTopology[i].normal, Vector3.up),
                        Color.blue,
                        boxFullDims * 0.5f)
                );
                ++boxesDisplayed;
            }

            // Vis description
            if (locationCount == boxesDisplayed)
            {
                AppState.Instance.SpaceQueryDescription = string.Format("{0} ({1})", visDesc, locationCount);
            }
            else
            {
                AppState.Instance.SpaceQueryDescription = string.Format("{0} (found={1}, displayed={2})", visDesc, locationCount, boxesDisplayed);
            }
        }

        private void HandleResults_Shape(string visDesc, int shapeCount, Color color, Vector3 defaultHalfDims)
        {
            // First clear all our geo
            ClearGeometry();

            // Only if we're enabled
            if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
            {
                return;
            }

            // Alignment information
            SpatialUnderstandingDll.Imports.QueryPlayspaceAlignment(SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignmentPtr());
            SpatialUnderstandingDll.Imports.PlayspaceAlignment alignment = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignment();

            // Add the line boxes (we may have more results than boxes - pick evenly across the results in that case)
            int lineInc = Mathf.CeilToInt((float)shapeCount / (float)DisplayResultMaxCount);
            int boxesDisplayed = 0;
            for (int i = 0; i < shapeCount; i += lineInc)
            {
                float timeDelay = (float)lineBoxList.Count * AnimatedBox.DelayPerItem;
                lineBoxList.Add(
                    new AnimatedBox(
                        timeDelay,
                        resultsShape[i].position,
                        Quaternion.LookRotation(alignment.BasisZ, alignment.BasisY),
                        Color.blue,
                        (resultsShape[i].halfDims.sqrMagnitude < 0.01f) ? defaultHalfDims : resultsShape[i].halfDims)
                );
                ++boxesDisplayed;
            }

            // Vis description
            if (shapeCount == boxesDisplayed)
            {
                AppState.Instance.SpaceQueryDescription = string.Format("{0} ({1})", visDesc, shapeCount);
            }
            else
            {
                AppState.Instance.SpaceQueryDescription = string.Format("{0} (found={1}, displayed={2})", visDesc, shapeCount, boxesDisplayed);
            }
        }

        private bool Draw_LineBoxList()
        {
            bool needsUpdate = false;
            for (int i = 0; i < lineBoxList.Count; ++i)
            {
                needsUpdate |= Draw_AnimatedBox(lineBoxList[i]);
            }
            return needsUpdate;
        }

        private void Update_Queries()
        {
            // Queries - basics
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                ClearGeometry();
            }
            if (Input.GetKeyDown(KeyCode.F))
            {
                Query_PlayspaceAlignment();
            }

            // Queries - topology
            if (Input.GetKeyDown(KeyCode.G))
            {
                Query_Topology_FindPositionOnWall();
            }
            if (Input.GetKeyDown(KeyCode.H))
            {
                Query_Topology_FindLargePositionsOnWalls();
            }
            if (Input.GetKeyDown(KeyCode.J))
            {
                Query_Topology_FindLargeWall();
            }
            if (Input.GetKeyDown(KeyCode.K))
            {
                Query_Topology_FindPositionsOnFloor();
            }
            if (Input.GetKeyDown(KeyCode.L))
            {
                Query_Topology_FindLargestPositionsOnFloor();
            }
            if (Input.GetKeyDown(KeyCode.Semicolon))
            {
                Query_Topology_FindPositionsPlaceable();
            }

            // Queries - shapes
            if (Input.GetKeyDown(KeyCode.Quote))
            {
                Query_Shape_FindShapeHalfDims("All Surfaces");
            }
            if (Input.GetKeyDown(KeyCode.Z))
            {
                Query_Shape_FindPositionsOnShape("Sittable");
            }
            if (Input.GetKeyDown(KeyCode.X))
            {
                Query_Shape_FindShapeHalfDims("Chair");
            }
            if (Input.GetKeyDown(KeyCode.C))
            {
                Query_Shape_FindShapeHalfDims("Large Surface");
            }
            if (Input.GetKeyDown(KeyCode.V))
            {
                Query_Shape_FindShapeHalfDims("Large Empty Surface");
            }
            if (Input.GetKeyDown(KeyCode.B))
            {
                Query_Shape_FindShapeHalfDims("Couch");
            }
        }

        private void Update()
        {
            // Queries
            if (SpatialUnderstanding.Instance.ScanState == SpatialUnderstanding.ScanStates.Done)
            {
                Update_Queries();
            }

            // Lines: Begin
            LineDraw_Begin();

            // Drawers
            bool needsUpdate = false;
            needsUpdate |= Draw_LineBoxList();

            // Lines: Finish up
            LineDraw_End(needsUpdate);
        }
    }
}
