using System;
using System.Collections.Generic;
using UnityEngine;

/*该scrip用于创建复数条ray于视角内,并探测被ray射到的物体*/

public class RaySensors : MonoBehaviour
{
    public Camera agentCam;
    public Camera TPSCam;
    public Material lineMeterial;
    public GameObject rayInfoPrefab;
    public GameObject agentCanvas;

    [SerializeField, Range(0, 500)] public float viewDistance = 100; // how long the ray can detect

    //[SerializeField, Range(0, 1)] public float totalRange = 1f; // Total view range Max = 1
    [SerializeField, Range(0, 1)] public float focusRange = 0.15f; // center focus range

    public int halfOuterRayNum = 7; // >=2
    public int focusRayNum = 5; // >= 1 and must be odd num!

    [Header("InGameLineSetting")]
    public bool showInGameRay = true;

    public bool showDebugRay = true;
    public bool showInGameRayInfo = true;
    public float lineWidth = 0.05f;

    [Header("RayCastResult")]
    public float[] rayTagResult;

    public List<float> rayTagResultOneHot;
    public float[] rayDisResult;

    [System.NonSerialized] public int totalRayNum;
    private string myTag = "";
    private GameObject[] linesOBJ;
    private GameObject[] rayInfoOBJ;
    private LineRenderer[] lineRenderers;
    private RayInfoUI[] rayInfoUIs;
    public List<GameObject> inViewEnemies = new List<GameObject>();
    private List<string> tags = new List<string> { "Wall", "Enemy" };
    private Onehot oneHotTags = new Onehot();

    private void Start()
    {
        myTag = gameObject.tag;
        totalRayNum = halfOuterRayNum * 2 + focusRayNum;
        rayTagResult = new float[totalRayNum];
        rayDisResult = new float[totalRayNum];
        linesOBJ = new GameObject[totalRayNum];
        lineRenderers = new LineRenderer[totalRayNum];
        rayInfoOBJ = new GameObject[totalRayNum];
        rayInfoUIs = new RayInfoUI[totalRayNum];
        oneHotTags.Initialize(tags);
        for (int i = 0; i < totalRayNum; i++)
        {
            linesOBJ[i] = new GameObject();
            linesOBJ[i].name = "rayCastLine-" + Convert.ToString(i);
            linesOBJ[i].transform.parent = agentCam.transform;
            linesOBJ[i].AddComponent<LineRenderer>();
            lineRenderers[i] = linesOBJ[i].GetComponent<LineRenderer>();
            lineRenderers[i].material = lineMeterial;

            rayInfoOBJ[i] = (GameObject)Instantiate(rayInfoPrefab);
            rayInfoOBJ[i].transform.SetParent(agentCanvas.transform, false);
            rayInfoOBJ[i].name = "rayInfo-" + Convert.ToString(i);
            rayInfoUIs[i] = rayInfoOBJ[i].GetComponent<RayInfoUI>();
        }
    }

    public int TagToInt(string tag)
    {
        switch (tag)
        {
            case "Wall":
                return 1;

            default:
                if (tag != myTag)
                {
                    return 2;
                }
                return 0;
        }
    }

    private void SingleRaycastUpdate(Ray ray, LineRenderer lineRenderer, RayInfoUI rayInfoUI, out float rayTagResult, out float rayDisResult)
    {
        // get Raycast hit infomation and return Tag and distance
        RaycastHit nowHit;
        Color rayColor = Color.cyan;
        float lineLength = viewDistance;
        string rayInfoText = "";
        Vector3 rayInfoPosition;
        if (Physics.Raycast(ray, out nowHit, viewDistance)) // 若在viewDistance范围内有碰撞
        {
            rayInfoText = nowHit.collider.tag;
            rayTagResult = TagToInt(rayInfoText);
            rayTagResultOneHot.AddRange(oneHotTags.Encoder(rayInfoText));
            rayDisResult = nowHit.distance;
            lineLength = rayDisResult;
            rayInfoText += "\n" + Convert.ToString(rayDisResult);
            // rayDisResult = rayDisResult / viewDistance; // Normalization!
            // 输出log
            switch (rayTagResult)
            {
                case 1:// Wall
                    rayColor = Color.white;
                    break;

                case 2: // Enemy
                    rayColor = Color.red;
                    inViewEnemies.Add(nowHit.transform.gameObject);
                    break;

                case -1: // Hit Nothing
                    rayColor = Color.gray;
                    break;

                default: // default,got wrong
                    rayColor = Color.cyan;
                    break;
            }
        }
        else // 若在viewDistance范围无碰撞
        {
            rayTagResultOneHot.AddRange(oneHotTags.Encoder());
            rayTagResult = -1f;
            rayDisResult = -1f;
            //输出log
            //Debug.Log(0);
            //Debug.Log(0);
        }
        rayInfoPosition = ray.origin + (ray.direction * lineLength);
        if (showInGameRay)
        {
            DrawLine(ray, lineLength, lineRenderer, rayColor);
        }
        else
        {
            TurnOffLine(lineRenderer, rayColor);
        }
        // drawRay in game
        if (showInGameRayInfo) rayInfoUI.UpdateInfo(rayInfoText, rayInfoPosition, rayColor, TPSCam);
        // Show log
        if (showDebugRay) Debug.DrawRay(ray.origin, ray.direction * viewDistance, rayColor); // drawRay in debug
        // Debug.Log(ray.origin + ray.direction);
        // Debug.Log(rayTagResult);
        // Debug.Log(tagToInt(nowHit.collider.tag));
    }

    private void DrawLine(Ray ray, float lineLength, LineRenderer lineRenderer, Color lineColor)
    {
        lineRenderer.startColor = lineColor;
        lineRenderer.endColor = lineColor;
        lineRenderer.startWidth = lineWidth;
        lineRenderer.endWidth = lineWidth;
        lineRenderer.SetPosition(0, ray.origin);
        lineRenderer.SetPosition(1, ray.origin + (ray.direction * lineLength));
    }

    private void TurnOffLine(LineRenderer lineRenderer, Color lineColor)
    {
        lineRenderer.startColor = lineColor;
        lineRenderer.endColor = lineColor;
        lineRenderer.startWidth = 0f;
        lineRenderer.endWidth = 0f;
        lineRenderer.SetPosition(0, new Vector3(0, 0, 0));
        lineRenderer.SetPosition(1, new Vector3(0, 0, 0));
    }

    public void UpdateRayInfo()
    {
        float focusLEdge = agentCam.pixelWidth * (1 - focusRange) / 2;
        float focusREdge = agentCam.pixelWidth * (1 + focusRange) / 2;
        float camPixelHeight = agentCam.pixelHeight;
        inViewEnemies.Clear();
        rayTagResultOneHot.Clear();
        for (int i = 0; i < halfOuterRayNum; i++) // create left outside rays; 0 ~ focusLeftEdge
        {
            Vector3 point = new Vector3(i * focusLEdge / (halfOuterRayNum - 1), camPixelHeight / 2, 0);
            Ray nowRay = agentCam.ScreenPointToRay(point);
            SingleRaycastUpdate(nowRay, lineRenderers[i], rayInfoUIs[i], out rayTagResult[i], out rayDisResult[i]);
        }
        for (int i = 0; i < halfOuterRayNum; i++) // create right outside rays; focusRightEdge ~ MaxPixelHeight
        {
            Vector3 point = new Vector3(focusREdge + (i * focusLEdge / (halfOuterRayNum - 1)), camPixelHeight / 2, 0);
            Ray nowRay = agentCam.ScreenPointToRay(point);
            SingleRaycastUpdate(nowRay, lineRenderers[halfOuterRayNum + i], rayInfoUIs[halfOuterRayNum + i], out rayTagResult[halfOuterRayNum + i], out rayDisResult[halfOuterRayNum + i]);
        }
        for (int i = 0; i < focusRayNum; i++) // create center focus rays; focusLeftEdge ~ focusLeftEdge
        {
            Vector3 point = new Vector3(focusLEdge + ((i + 1) * (focusREdge - focusLEdge) / (focusRayNum + 1)), camPixelHeight / 2, 0);
            Ray nowRay = agentCam.ScreenPointToRay(point);
            SingleRaycastUpdate(nowRay, lineRenderers[halfOuterRayNum * 2 + i], rayInfoUIs[halfOuterRayNum * 2 + i], out rayTagResult[halfOuterRayNum * 2 + i], out rayDisResult[halfOuterRayNum * 2 + i]);
        }
    }
}