﻿using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[ExecuteInEditMode]
public class uGUIAnnotationPMIData : MonoBehaviour
{
    Font sSymbolFont = null;
    GameObject annotationObject = null;

    void Start()
    {
        // Check if annotations have already been created
        if (null != annotationObject)
            return;

        // Retrieve PMI data and create annotations based on PMI data
        PMIData data = this.GetComponent<PMIData>();
        if (null == data || null == data.pmiData)
            return;
        CreatePMIDataAnnotation(data.pmiData);
    }

    // Create PMI objects
    void CreatePMIDataAnnotation(PMIDataAsset pmiData)
    {
        // Load symbol font
        if (null == sSymbolFont)
        {
            sSymbolFont = Resources.LoadAssetAtPath<Font>("Assets/Annotation/Font/Y14.5M-2009.ttf");
            if (null == sSymbolFont)
                sSymbolFont = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font;
        }

        annotationObject = new GameObject(this.gameObject.name + " Annotation");

        // Create annotations based on each available type
        foreach (PMIGeomTol geomTol in pmiData.geomTols)
            CreatePMIGeomTol(geomTol, annotationObject);
        foreach (PMISymbol symbol in pmiData.symbols)
            CreatePMISymbol(symbol, annotationObject);
        foreach (PMIDatumTarget datumTarget in pmiData.datumTargets)
            CreatePMIDatumTarget(datumTarget, annotationObject);
        foreach (PMIDimension dimension in pmiData.dimensions)
            CreatePMIDimension(dimension, annotationObject);

        // Attach annotation object to the current game object
        annotationObject.transform.parent = this.transform;
        annotationObject.transform.localRotation = Quaternion.identity;
        annotationObject.transform.localPosition = Vector3.zero;
    }

    GameObject CreatePMIGeomTol(PMIGeomTol pmiGeomTol, GameObject parent)
    {
        // Create world space canvas
        GameObject canvasObject = new GameObject("Geom Tol", typeof(Canvas));
        Canvas uGUICanvas = canvasObject.GetComponent<Canvas>();
        uGUICanvas.renderMode = RenderMode.WorldSpace;

        // Create leaders (arrows)
        foreach (PMILeader pmiLeader in pmiGeomTol.leaders)
            CreateAnnotationLine(pmiLeader.polyline, canvasObject);

        // Prepare for calculation of panel size
        Vector3 minPanel = new Vector3(int.MaxValue, int.MaxValue, int.MaxValue);
        Vector3 maxPanel = new Vector3(int.MinValue, int.MinValue, int.MinValue);

        // Create reference block texts
        foreach (PMITolSpecList pmiTolSpecList in pmiGeomTol.tolSpecLists)
        {
            // Create reference block type symbol
            string symbolText = "";
            switch (pmiTolSpecList.type)
            {
                case 1:
                    symbolText += '\u00B6';
                    break;
                case 2:
                    symbolText += '\u00B8';
                    break;
                case 3:
                    symbolText += '\u00B4';
                    break;
                case 4:
                    symbolText += '\u00B3';
                    break;
                case 5:
                    symbolText += '\u00B9';
                    break;
                case 6:
                    symbolText += '\u00BA';
                    break;
                case 7:
                    symbolText += '\u00BB';
                    break;
                case 8:
                    symbolText += '\u00B2';
                    break;
                case 9:
                    symbolText += '\u00BC';
                    break;
                case 10:
                    symbolText += '\u00B7';
                    break;
                case 11:
                    symbolText += '\u00BF';
                    break;
                case 12:
                    symbolText += '\u00B5';
                    break;
                case 13:
                    symbolText += '\u00BB';
                    break;
                case 14:
                    symbolText += '\u00BD';
                    break;
                case 15:
                    symbolText += '\u00BE';
                    break;
                default:
                    break;
            }

            GameObject annotationTextObject = CreateAnnotationText(symbolText, pmiTolSpecList.typeLocation, 0, canvasObject);
            GetAnnotationTextSize(annotationTextObject, ref minPanel, ref maxPanel);

            foreach (PMITolSpec pmiTolSpec in pmiTolSpecList.tolSpecs)
            {
                annotationTextObject = CreateAnnotationText(pmiTolSpec.text, canvasObject);
                GetAnnotationTextSize(annotationTextObject, ref minPanel, ref maxPanel);

                foreach (PMIReferenceBlock pmiReferenceBlock in pmiTolSpec.referenceBlocks)
                {
                    string referenceBlockText = "";
                    foreach (PMIReferenceLabel pmiReferenceLabel in pmiReferenceBlock.referenceLabels)
                        referenceBlockText += pmiReferenceLabel.text;

                    annotationTextObject = CreateAnnotationText(referenceBlockText, pmiReferenceBlock.location, pmiTolSpec.text.rotation, canvasObject);
                    GetAnnotationTextSize(annotationTextObject, ref minPanel, ref maxPanel);
                }
            }
        }

        // Create back panel for reference block
        CreateAnnotationPanel(minPanel, maxPanel, canvasObject);

        // Set transform
        canvasObject.transform.position = pmiGeomTol.location;
        canvasObject.transform.rotation = pmiGeomTol.rotation;
        canvasObject.transform.localScale = pmiGeomTol.scale;
        canvasObject.transform.SetParent(parent.transform);

        return canvasObject;
    }

    GameObject CreatePMISymbol(PMISymbol pmiSymbol, GameObject parent)
    {
        // Create world space canvas
        GameObject canvasObject = new GameObject("Symbol", typeof(Canvas));
        Canvas uGUICanvas = canvasObject.GetComponent<Canvas>();
        uGUICanvas.renderMode = RenderMode.WorldSpace;

        // Create leaders (arrows)
        foreach (PMILeader pmiLeader in pmiSymbol.leaders)
            CreateAnnotationLine(pmiLeader.polyline, canvasObject);

        // Create symbol texts
        foreach (PMIText pmiText in pmiSymbol.texts)
            CreateAnnotationText(pmiText, canvasObject);

        // Set transform
        canvasObject.transform.position = pmiSymbol.location;
        canvasObject.transform.rotation = pmiSymbol.rotation;
        canvasObject.transform.localScale = pmiSymbol.scale;
        canvasObject.transform.SetParent(parent.transform);

        return canvasObject;
    }

    GameObject CreatePMIDatumTarget(PMIDatumTarget pmiDatumTarget, GameObject parent)
    {
        // Create world space canvas
        GameObject canvasObject = new GameObject("Datum Target", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
        Canvas uGUICanvas = canvasObject.GetComponent<Canvas>();
        uGUICanvas.renderMode = RenderMode.WorldSpace;

        // Create leaders (arrows)
        foreach (PMILeader pmiLeader in pmiDatumTarget.leaders)
            CreateAnnotationLine(pmiLeader.polyline, canvasObject);

        // Create texts
        foreach (PMIText pmiText in pmiDatumTarget.topText)
            CreateAnnotationText(pmiText, canvasObject);
        foreach (PMIText pmiText in pmiDatumTarget.bottomText)
            CreateAnnotationText(pmiText, canvasObject);

        // Create datum target circle
        Vector3[] points = new Vector3[160];
        for (int i = 0; i < 160; i++)
            points[i] = pmiDatumTarget.center + new Vector3(pmiDatumTarget.radius * Mathf.Cos(2 * Mathf.PI * i / 160), pmiDatumTarget.radius * Mathf.Sin(2 * Mathf.PI * i / 160), 0);
        CreateAnnotationLine(points, canvasObject);

        points = new Vector3[2];
        points[0] = pmiDatumTarget.center - new Vector3(pmiDatumTarget.radius, 0, 0);
        points[1] = pmiDatumTarget.center + new Vector3(pmiDatumTarget.radius, 0, 0);
        CreateAnnotationLine(points, canvasObject);

        // Set transform
        canvasObject.transform.position = pmiDatumTarget.location;
        canvasObject.transform.rotation = pmiDatumTarget.rotation;
        canvasObject.transform.localScale = pmiDatumTarget.scale;
        canvasObject.transform.SetParent(parent.transform);

        return canvasObject;
    }

    GameObject CreatePMIDimension(PMIDimension pmiDimension, GameObject parent)
    {
        // Create world space canvas
        GameObject canvasObject = new GameObject("Dimension", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
        Canvas uGUICanvas = canvasObject.GetComponent<Canvas>();
        uGUICanvas.renderMode = RenderMode.WorldSpace;

        // Create leaders (arrows)
        foreach (PMILeader pmiLeader in pmiDimension.leaders)
            CreateAnnotationLine(pmiLeader.polyline, canvasObject);

        // Create dimension texts
        foreach (PMIText pmiText in pmiDimension.texts)
            CreateAnnotationText(pmiText, canvasObject);

        // Create dimension lines
        foreach (PMIPolyline pmiPolyline in pmiDimension.polylines)
            CreateAnnotationLine(pmiPolyline, canvasObject);

        // Set transform
        canvasObject.transform.position = pmiDimension.location;
        canvasObject.transform.rotation = pmiDimension.rotation;
        canvasObject.transform.localScale = pmiDimension.scale;
        canvasObject.transform.SetParent(parent.transform);

        return canvasObject;
    }

    // Create uGUI objects
    GameObject CreateAnnotationText(PMIText pmiText, GameObject parent)
    {
        GameObject annotationText = new GameObject(pmiText.text, typeof(RectTransform), typeof(CanvasRenderer), typeof(Text));
        annotationText.layer = 5;
        annotationText.transform.SetParent(parent.transform);

        Text label = annotationText.GetComponent<Text>();
        label.text = pmiText.text;
        label.font = pmiText.font;
        label.fontSize = (int)pmiText.characterHeight;
        label.lineSpacing = pmiText.lineSpacing + 1;

        RectTransform rectT = annotationText.GetComponent<RectTransform>();
        rectT.pivot = new Vector2(0.5f, 0.5f);
        rectT.sizeDelta = new Vector2(label.preferredWidth, label.preferredHeight);
        rectT.Rotate(0, 180, pmiText.rotation);
        rectT.position = pmiText.location;

        return annotationText;
    }

    GameObject CreateAnnotationText(string text, Vector3 location, float rotation, GameObject parent)
    {
        GameObject annotationText = new GameObject(text, typeof(RectTransform), typeof(CanvasRenderer), typeof(Text));
        annotationText.layer = 5;
        annotationText.transform.SetParent(parent.transform);

        Text label = annotationText.GetComponent<Text>();
        label.text = text;
        label.font = sSymbolFont;

        RectTransform rectT = annotationText.GetComponent<RectTransform>();
        rectT.pivot = new Vector2(0.5f, 0.5f);
        rectT.sizeDelta = new Vector2(label.preferredWidth, label.preferredHeight);
        rectT.Rotate(0, 180, rotation);
        rectT.position = location;

        return annotationText;
    }

    GameObject CreateAnnotationLeader(PMILeader pmiLeader, GameObject parent)
    {
        GameObject annotationLeaderObject = new GameObject("Leader", typeof(LineRenderer));
        annotationLeaderObject.transform.SetParent(parent.transform);

        LineRenderer annotationLeader = annotationLeaderObject.GetComponent<LineRenderer>();

        annotationLeader.material = new Material(Shader.Find("Standard"));
        annotationLeader.shadowCastingMode = ShadowCastingMode.Off;
        annotationLeader.receiveShadows = false;
        annotationLeader.useLightProbes = false;
        annotationLeader.useWorldSpace = false;

        annotationLeader.SetColors(Color.white, Color.white);
        annotationLeader.SetVertexCount(pmiLeader.polyline.linePoints.Length);

        int i = 0;
        foreach (Vector3 point in pmiLeader.polyline.linePoints)
            annotationLeader.SetPosition(i++, point);

        // TODO: Create arrowheads

        return annotationLeaderObject;
    }

    GameObject CreateAnnotationLine(PMIPolyline pmiPolyline, GameObject parent)
    {
        GameObject annotationLineObject = new GameObject("Line", typeof(LineRenderer));
        annotationLineObject.transform.SetParent(parent.transform);

        LineRenderer annotationLine = annotationLineObject.GetComponent<LineRenderer>();
        annotationLine.material = new Material(Shader.Find("Standard"));
        annotationLine.shadowCastingMode = ShadowCastingMode.Off;
        annotationLine.receiveShadows = false;
        annotationLine.useLightProbes = false;
        annotationLine.useWorldSpace = false;

        annotationLine.SetColors(Color.white, Color.white);
        annotationLine.SetVertexCount(pmiPolyline.linePoints.Length);

        int i = 0;
        foreach (Vector3 point in pmiPolyline.linePoints)
            annotationLine.SetPosition(i++, point);

        return annotationLineObject;
    }

    GameObject CreateAnnotationLine(Vector3[] points, GameObject parent)
    {
        GameObject annotationLineObject = new GameObject("Line", typeof(LineRenderer));
        annotationLineObject.transform.SetParent(parent.transform);

        LineRenderer annotationLine = annotationLineObject.GetComponent<LineRenderer>();
        annotationLine.material = new Material(Shader.Find("Standard"));
        annotationLine.shadowCastingMode = ShadowCastingMode.Off;
        annotationLine.receiveShadows = false;
        annotationLine.useLightProbes = false;
        annotationLine.useWorldSpace = false;

        annotationLine.SetColors(Color.white, Color.white);
        annotationLine.SetVertexCount(points.Length);

        int i = 0;
        foreach (Vector3 point in points)
            annotationLine.SetPosition(i++, point);

        return annotationLineObject;
    }

    GameObject CreateAnnotationPanel(Vector3 minPanel, Vector3 maxPanel, GameObject parent)
    {
        GameObject annotationPanelObject = new GameObject("Panel", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
        annotationPanelObject.layer = 5;
        annotationPanelObject.transform.SetParent(parent.transform);

        Image panelImage = annotationPanelObject.GetComponent<Image>();
        panelImage.type = Image.Type.Sliced;
        panelImage.color = new Color(1f, 1f, 1f, 0.392f);

        RectTransform rectT = annotationPanelObject.GetComponent<RectTransform>();
        Vector2 size = maxPanel - minPanel;
        rectT.position = (minPanel + maxPanel) / 2;
        rectT.sizeDelta = new Vector2(size.x + 6, size.y + 6);

        return annotationPanelObject;
    }

    // Helper functions
    void GetAnnotationTextSize(GameObject annotationText, ref Vector3 minPanel, ref Vector3 maxPanel)
    {
        RectTransform rectT = annotationText.GetComponent<RectTransform>();
        Vector3 halfPanelSize = new Vector3(rectT.sizeDelta.x, rectT.sizeDelta.y, 0) / 2;
        minPanel = Vector3.Min(minPanel, rectT.position - halfPanelSize);
        maxPanel = Vector3.Max(maxPanel, rectT.position + halfPanelSize);
    }
}
