using System;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using static GadgetBehaviour;
using UnityEngine.InputSystem;
using MoreLinq;
using System.Linq;
//TODO check whether this can be deleted 
//using System.Linq;
//using static GadgetManager;

public class WorldCursor : MonoBehaviour
{
    public RaycastHit Hit;
    // TODO experimentell for multiple hits
    public RaycastHit[] Hits;

    public string deactivateSnapKey;
    private Camera Cam;
    private int layerMask;

    public float MaxRange = 10f;
    public bool useCamCurser = false;

    private void Awake()
    {
        //Ignore player and TalkingZone
        this.layerMask = ~LayerMask.GetMask("Player", "TalkingZone");
    }

    void Start()
    {
        Cam = Camera.main;
        CultureInfo.CurrentCulture = new CultureInfo("en-US");
    }

    public void setLayerMask(int layerMask)
    {
        this.layerMask = layerMask;
    }

    // working currently to include multiple hits 
    // TODO 

    void Update()
    {
        updateMaxRange();

        Cam = Camera.main; //WARN: Should not called every Update; TODO: Cache in Start/Awake?
        Vector3 mousePos = UIconfig.InputManagerVersion switch
        {
            1 or 2 or 3 => Input.mousePosition,
            _ => Vector3.zero
        };

        Ray ray = useCamCurser
            ? new Ray(Cam.transform.position, Cam.transform.forward)
            : Cam.ScreenPointToRay(mousePos);

        transform.up = Cam.transform.forward;
        transform.position = ray.GetPoint(GlobalBehaviour.GadgetPhysicalDistance);

        //************************************************
        bool deactSnapKey =
               Input.GetButton(this.deactivateSnapKey)
            && UIconfig.InputManagerVersion >= 1
            && UIconfig.InputManagerVersion <= 3;
        //************************************************

        Hits = Physics.RaycastAll(ray, MaxRange, layerMask);
        if (Hits.Length == 0)
        {
            Hits = Physics.RaycastAll(transform.position, Vector3.down, GlobalBehaviour.GadgetPhysicalDistance, layerMask);
            if (Hits.Length == 0)
                return; // in case we dont hit anything, just return
        }
        Hits = Hits.OrderBy(h => h.distance).ToArray();

        Fact[] facts = Hits
            .Select(h =>
                h.transform.TryGetComponent(out FactObject factObj)
                ? FactOrganizer.AllFacts[factObj.URI]
                : null)
            .ToArray();

        int i = 0;
        for (; i < Hits.Length; i++)
        {
            // check whether we actually hit something 
            if (deactSnapKey ||
               (!Hits[i].collider.transform.CompareTag("SnapZone")
             && !Hits[i].collider.transform.CompareTag("Selectable")))
                continue;


            if (facts[i] is AbstractLineFact lineFact)
            {
                Hits[i].point =
                    Math3d.ProjectPointOnLine(lineFact.Point1.Point, lineFact.Dir, Hits[i].point);
            }
            else if (facts[i] is CircleFact circleFact)
            {
                var projPlane =
                    Math3d.ProjectPointOnPlane(circleFact.normal, circleFact.Point1.Point, Hits[i].point);

                var circleDistance = projPlane - circleFact.Point1.Point;
                if (circleDistance.magnitude >= circleFact.radius)
                {
                    projPlane = circleFact.Point1.Point + circleDistance.normalized * circleFact.radius;
                    Hits[i].normal = circleDistance.normalized;
                }

                Hits[i].point = projPlane;
            }
            else
            {
                Hits[i].point = Hits[i].collider.transform.position;
                Hits[i].normal = Vector3.up;
            }

            // checking for 2 lines intersection point
            if (Hits.Length <= i + 1
             || Mathf.Abs(Hits[i + 0].distance - Hits[i + 1].distance) > 0.1)
                break; // we had at least 1 succesfull case

            // we probably have two objects intersecting

            if (facts[i + 0] is RayFact rayFact1
             && facts[i + 1] is RayFact rayFact2)
            {
                if (Math3d.LineLineIntersection(out Vector3 intersectionPoint
                    , rayFact2.Point1.Point, rayFact2.Dir
                    , rayFact1.Point1.Point, rayFact1.Dir))
                {
                    Hits[i].point = intersectionPoint;
                    break;
                }
            }

            //TODO: check for other types of intersection. Future Work

            break; // we had at least 1 succesfull case
        }

        int nr_hits = Hits.Length - i;
        if (i == Hits.Length)
        {
            i = 0;
            nr_hits = 1;
        }
        Hits = Hits.Slice(i, nr_hits).ToArray();
        Hit = Hits[0];

        transform.up = Hit.normal;
        transform.position = Hit.point + .01f * Hit.normal;

        if (Input.GetMouseButtonDown(0))
            ClickHandler();
    }

    void updateMaxRange()
    {
        switch (UIconfig.GameplayMode)
        {
            case 2:
                UIconfig.interactingRangeMode = UIconfig.InteractingRangeMode.fromObserverView;
                break;

            case 5:
            case 6:
                UIconfig.interactingRangeMode = UIconfig.InteractingRangeMode.fromCharacterView;
                break;

            default:
                break;
        }
    }

    //Check if left Mouse-Button was pressed and handle it
    void ClickHandler()
    {
        //TODO edit for the multiple hits. Right now it only checks the first hit

        if (Hit.collider.transform.CompareTag("NPC1_text")
         && UIconfig.nextDialogPlease < 2)
            UIconfig.nextDialogPlease++;

        if (IsPointerOverUIObject() //Needed for Android. NOT JUST: EventSystem.current.IsPointerOverGameObject()
         || Hit.transform.gameObject.layer == LayerMask.NameToLayer("Water") // not allowed to meassure on water
         || Hit.collider.transform.CompareTag("NPC1_text"))                  // not allowed to meassure on textfields
            return;

        //if (Hit.transform.gameObject.layer == LayerMask.NameToLayer("TransparentFX")) return; // not allowed to meassure on TransparentFX
        CommunicationEvents.TriggerEvent.Invoke(Hits);
        return;


        static bool IsPointerOverUIObject()
        {
            PointerEventData eventDataCurrentPosition = new(EventSystem.current)
            {
                position = new Vector2(Input.mousePosition.x, Input.mousePosition.y)
            };

            List<RaycastResult> results = new();
            EventSystem.current.RaycastAll(eventDataCurrentPosition, results);

            return results.Count > 0;
        }
    }
}