using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.Networking;
using Newtonsoft.Json;
using System.Linq;
using static CommunicationEvents;
using static SOMDocManager;

public class ScrollDetails : MonoBehaviour
{
    public WorldCursor cursor;
    public GameObject parameterDisplayPrefab;
    public Scroll ActiveScroll;
    public GameObject mmtAnswerPopUp;
    private PopupBehavior Popup;

    public static List<RenderedScrollFact> ParameterDisplays;
    private static List<Scroll.ScrollAssignment> LatestCompletions;
    private static List<Fact> LatestRenderedHints;

    public string currentMmtAnswer;

    public bool DynamicScrollDescriptionsActive = true;
    public bool AutomaticHintGenerationActive = true;


    void Awake()
    {
        if (cursor == null)
            cursor = FindObjectOfType<WorldCursor>();

        (Popup = mmtAnswerPopUp.GetComponent<PopupBehavior>())
            .hidePopUp();
    }

    private void OnEnable()
    {
        ScrollFactHintEvent.AddListener(animateHint);
        NewAssignmentEvent.AddListener(NewAssignmentTrigger);
        RemoveFactEvent.AddListener(removeFactFromAssignment);
    }

    private void OnDisable()
    {
        ScrollFactHintEvent.RemoveListener(animateHint);
        NewAssignmentEvent.RemoveListener(NewAssignmentTrigger);
        RemoveFactEvent.RemoveListener(removeFactFromAssignment);
    }

    public void SetScroll(Scroll scroll_to_set)
    {
        ActiveScroll = scroll_to_set;

        Transform originalScroll = gameObject.transform.GetChild(1);
        Transform originalScrollView = originalScroll.GetChild(1);
        Transform originalViewport = originalScrollView.GetChild(0);

        //Clear all current ScrollFacts
        originalViewport.GetChild(0).gameObject.DestroyAllChildren();

        originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = ActiveScroll.description;

        //ParameterDisplays.ForEach(gameObj => Destroy(gameObj));
        ParameterDisplays = new();
        for (int i = 0; i < ActiveScroll.requiredFacts.Count; i++)
        {
            GameObject originalObj =
                Instantiate(parameterDisplayPrefab, originalViewport.GetChild(0));

            RenderedScrollFact originalRSF =
                originalObj.GetComponentInChildren<RenderedScrollFact>();

            ParameterDisplays.Add(originalRSF);

            originalRSF.Populate(ActiveScroll, i);
        }

        foreach (int i in PrePopulateActiveScroll())
            ParameterDisplays[i].gameObject.SetActive(false);

        //set active scroll for ErrorMessagePopup
        Popup.setScroll(ActiveScroll);
        Popup.setParameterDisplays(ParameterDisplays.Select(RSF => RSF.gameObject).ToList());
    }

    /// <summary>
    /// Secretly populates <see cref="Scroll"/>s
    /// </summary>
    /// <returns><c>Array</c> containing indicis of <see cref="Scroll.requiredFacts"/> to be hidden.</returns>
    private int[] PrePopulateActiveScroll()
    {
        switch (ActiveScroll.@ref)
        {
            case MMT_OMS_URI.ScrollAngleSum:
                return new int[0];

            default:
                return new int[0];
        }
    }

    public void MagicButtonTrigger()
    {
        StartCoroutine(_MagicButton());

        IEnumerator _MagicButton()
        {
            yield return SendView("/scroll/apply");

            if (currentMmtAnswer == null)
            {
                Debug.LogError("Magic FAILED");
                PushoutFactFailEvent.Invoke(null, null);
            }
            else
            {
                if (VerboseURI)
                    Debug.Log("Magic answers:\n" + currentMmtAnswer);

                Scroll.ScrollApplicationInfo pushout = JsonConvert.DeserializeObject<Scroll.ScrollApplicationInfo>(currentMmtAnswer);

                if (pushout.acquiredFacts.Count == 0)
                    PushoutFactFailEvent.Invoke(null, pushout);

                ReadPushout(pushout.acquiredFacts);
            }
        }
    }

    public void NewAssignmentTrigger()
    {
        if (AutomaticHintGenerationActive || DynamicScrollDescriptionsActive)
            StartCoroutine(_NewAssignment());

        IEnumerator _NewAssignment()
        {
            //Non blocking wait till sendView() is finished
            yield return SendView("/scroll/dynamic");

            if (currentMmtAnswer == null)
            {
                Debug.LogError("Dynamic Scroll FAILED");
            }
            else
            {
                Scroll.ScrollDynamicInfo scrollDynamicInfo = JsonConvert.DeserializeObject<Scroll.ScrollDynamicInfo>(currentMmtAnswer);
                processScrollDynamicInfo(scrollDynamicInfo);
            }
        }
    }

    private IEnumerator SendView(string endpoint)
    {
        while (ParameterDisplays == null) // Wait for server
            yield return null;

        string body = prepareScrollAssignments();

        using UnityWebRequest www = UnityWebRequest.Put(ServerAdress + endpoint, body);
        www.method = UnityWebRequest.kHttpVerbPOST;
        www.SetRequestHeader("Content-Type", "application/json");

        System.DateTime sendTime = System.DateTime.UtcNow;
        yield return www.SendWebRequest();
        if (VerboseURI)
            Debug.LogFormat("Server answerd in : {0}ms"
                , (System.DateTime.UtcNow - sendTime).TotalMilliseconds);

        if (www.result == UnityWebRequest.Result.ConnectionError
         || www.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.Log(www.error);
            currentMmtAnswer = null;
        }
        else
        {
            string answer = www.downloadHandler.text;
            currentMmtAnswer = answer;
        }

        string prepareScrollAssignments()
        {
            List<Scroll.ScrollAssignment> assignmentList = new();

            for (int i = 0; i < ParameterDisplays.Count; i++)
            {
                Fact tempFact = ParameterDisplays[i].Fact;
                if (tempFact != null)
                    assignmentList.Add(new Scroll.ScrollAssignment(ActiveScroll.requiredFacts[i].@ref.uri, tempFact.Id));
            }

            return JsonConvert.SerializeObject(new Scroll.FilledScroll(ActiveScroll.@ref, assignmentList));
        }
    }

    private void ReadPushout(List<MMTDeclaration> pushoutFacts)
    {
        Popup.hidePopUp(); //close error Window

        bool samestep = false;
        for (int i = 0; i < pushoutFacts.Count; i++)
        {
            Fact newFact = ParsingDictionary.parseFactDictionary[pushoutFacts[i].getType()].Invoke(pushoutFacts[i]);
            if (newFact != null)
            {
                AnimateExistingFactEvent.Invoke
                    (FactManager.AddFactIfNotFound(newFact, out _, samestep, null, ActiveScroll.label).Id
                    , FactWrapper.FactMaterials.Solution);

                samestep = true;
            }
            else
                Debug.Log("Parsing on pushout-fact returned null -> One of the dependent facts does not exist");
        }
    }

    private void processScrollDynamicInfo(Scroll.ScrollDynamicInfo scrollDynamicInfo)
    {
        LatestCompletions = scrollDynamicInfo.completions.Count > 0
            ? scrollDynamicInfo.completions[0]
            : new List<Scroll.ScrollAssignment>();

        List<string> hintUris = LatestCompletions
            .Select(completion => completion.fact.uri)
            .ToList();

        //Update Scroll, process data for later hints and update Uri-List for which hints are available
        _processRenderedScroll(scrollDynamicInfo.rendered, hintUris);

        if (AutomaticHintGenerationActive)
            //Show that Hint is available for ScrollParameter
            HintAvailableEvent.Invoke(hintUris);

        return;

        void _processRenderedScroll(Scroll rendered, List<string> hintUris)
        {
            if (DynamicScrollDescriptionsActive)
            { // Update scroll-description
                Transform scroll = gameObject.transform.GetChild(1).transform;
                scroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = rendered.description;
            }

            LatestRenderedHints = new();
            for (int i = 0; i < rendered.requiredFacts.Count; i++)
            {
                RenderedScrollFact RenderedScrollFact = ParameterDisplays
                    .Find(RSF => RSF.ScrollFactURI == rendered.requiredFacts[i].@ref.uri);

                if (DynamicScrollDescriptionsActive)
                    //Update ScrollParameter label
                    RenderedScrollFact.Scroll = rendered;

                //If ScrollFact is assigned -> No Hint
                if (RenderedScrollFact.Fact == null)
                {
                    Fact HintFact =
                        ParsingDictionary.parseFactDictionary[rendered.requiredFacts[i].getType()]
                        .Invoke(rendered.requiredFacts[i]);

                    //If the fact could not be parsed -> Therefore not all dependent Facts exist -> No Hint
                    if (HintFact != null)
                    {
                        hintUris.Add(HintFact.Id);
                        LatestRenderedHints.Add(HintFact);
                    }
                }
            }

            return;
        }
    }

    public void animateHint(ImageHintAnimation scrollHintImage, string scrollParameterUri)
    {
        if (FactOrganizer.AllFacts.ContainsKey(scrollParameterUri))
            AnimateExistingFactEvent.Invoke(
                    scrollParameterUri,
                    FactWrapper.FactMaterials.Hint
                );

        Scroll.ScrollAssignment suitableCompletion =
            LatestCompletions.Find(x => x.fact.uri == scrollParameterUri);

        Fact fact;
        if (suitableCompletion != null)
        {
            if (FactOrganizer.AllFacts.ContainsKey(suitableCompletion.assignment.uri))
            {
                AnimateExistingFactEvent.Invoke(
                    suitableCompletion.assignment.uri,
                    FactWrapper.FactMaterials.Hint
                );
            }
        }
        else if (null !=
            (fact = LatestRenderedHints.Find(x => x.Id == scrollParameterUri)))
        {
            if (FactOrganizer.AllFacts.ContainsKey(fact.Id))
                // existing fact -> Animate that
                AnimateExistingFactEvent.Invoke(
                    fact.Id,
                    FactWrapper.FactMaterials.Hint
                );

            else
            {
                // Generate new FactRepresentation and animate it
                AnimateNonExistingFactEvent.Invoke(fact);
                AnimateExistingFactEvent.Invoke(
                    scrollParameterUri,
                    FactWrapper.FactMaterials.Hint
                );
            }
        }
    }

    //this is called whenever a Fact is Deleted in the world, to make sure it is removed from the scroll
    public void removeFactFromAssignment(Fact fact)
    {
        Transform originalScroll = gameObject.transform.GetChild(1).transform;
        Transform originalScrollView = originalScroll.GetChild(1);
        Transform originalViewport = originalScrollView.GetChild(0);

        for (int i = 0; i < originalViewport.GetChild(0).childCount; i++)
        {
            RenderedScrollFact scrollFact = originalViewport.GetChild(0).transform.GetChild(i).GetChild(0).gameObject.GetComponent<RenderedScrollFact>();
            if (scrollFact.ScrollFactLabel == fact.Label)
                scrollFact.OnPointerClick(null);
        }
    }
}