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

public class ScrollDetails : MonoBehaviour
{
    public static ScrollDetails Instance
    {
        get => _Instance;
        set
        {
            if (_Instance != value)
                Destroy(_Instance);

            _Instance = value;
        }
    }
    private static ScrollDetails _Instance;

    public WorldCursor cursor;
    public GameObject parameterDisplayPrefab;
    public static Scroll ActiveScroll { get; private set; }
    public GameObject mmtAnswerPopUp;
    private PopupBehavior Popup;

    public static List<RenderedScrollFact> ParameterDisplays { get; private set; }
    private static List<ScrollAssignment> LatestBackwartsCompletions;
    private static List<Fact> LatestRenderedHints;

    public string currentMmtAnswer;

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

    private bool SendingViewDone = true;
    private bool DynamicScrollInQue = false;
    private bool MagicInQue = false;

    private readonly IReadOnlyList<string> NoDynamicScroll = new List<string>()
    {
        // Insert ScrollURIS that are poorly optimized
    };

    void Awake()
    {
        Instance = this;

        if (cursor == null)
            cursor = FindObjectOfType<WorldCursor>();

        Popup = mmtAnswerPopUp.GetComponent<PopupBehavior>();
        Popup.gameObject.SetActive(true); // force Awake
    }

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

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

    private void OnDestroy()
    {
        _Instance = null;
    }

    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, ActiveScroll.requiredFacts[i].@ref.uri);
        }

        if (StageStatic.stage.solution.ScrollOverwrites
                .TryGetValue(ActiveScroll.ScrollReference, out (string, int, bool)[] population)
         && population.Length > 0)
        {
            DynamicScrollInQue = true; // block update on population

            foreach ((string Id, int index, bool show) in population)
            {
                ParameterDisplays[index].URI = Id;
                ParameterDisplays[index].transform.parent.gameObject.SetActive(show);
            }

            DynamicScrollInQue = false; // unblock
        }

        NewAssignmentEvent.Invoke(); // init display
    }

    public bool SetNextEmptyTo(FactObjectUI activator)
    {
        RenderedScrollFact check = ParameterDisplays
            .Find(RSF => RSF != null
                      && RSF.Payload != null
                      && RSF.Payload.Equals(activator));

        if (check != null)
        {
            check.URI = null;
            return false;
        }

        RenderedScrollFact empty = ParameterDisplays
            .Find(RSF => !RSF.IsSet);

        if (empty == null)
            return false;

        empty.SetByFactObject(activator);
        return true;
    }

    public void MagicButtonTrigger()
    {
        if (ActiveScroll == null)
            return;

        StartCoroutine(_MagicButton());

        IEnumerator _MagicButton()
        {
            if (MagicInQue)
                yield break;  // only need next in que to finish

            MagicInQue = true;
            while (!SendingViewDone || DynamicScrollInQue)
                yield return null; // Wait for last assignment
            MagicInQue = false;

            yield return SendView("/scroll/apply");

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

                System.DateTime serializeTime = System.DateTime.UtcNow;
                ScrollApplicationInfo pushout = JsonConvert.DeserializeObject<ScrollApplicationInfo>(currentMmtAnswer);
                Debug.LogFormat($"Answerd serialized in : {(System.DateTime.UtcNow - serializeTime).TotalMilliseconds}ms");

                if (pushout.acquiredFacts.Count == 0
                 || pushout.errors.Length > 0)
                {
                    ScrollApplicationCheckingErrorEvent.Invoke(pushout.errors);
                    PushoutFactFailEvent.Invoke();
                }
                else
                    Popup.HidePopUp(); //close error Window

                yield return __GeneratePushoutFacts(pushout.acquiredFacts);
            }
        }

        IEnumerator __GeneratePushoutFacts(List<MMTFact> pushoutFacts)
        {
            List<Fact> new_facts = new();
            Dictionary<string, string> old_to_new = new();
            System.DateTime parseTime = System.DateTime.UtcNow;

            bool samestep = false;
            for (int i = 0; i < pushoutFacts.Count; i++)
            {
                List<Fact> new_list = Fact.MMTFactory(pushoutFacts[i].MapURIs(old_to_new));

                if (new_list.Count == 0)
                {
                    Debug.LogWarning("Parsing on pushout-fact returned empty List -> One of the dependent facts does not exist or parsing failed");
                    continue;
                }

                foreach (Fact new_fact in new_list)
                {
                    Fact added = FactAdder.AddFactIfNotFound(new_fact, out bool exists, samestep, null, ActiveScroll.label);
                    if (!exists)
                    {
                        new_facts.Add(added);
                        AnimateExistingFactEvent.Invoke(added.Id, FactWrapper.FactMaterials.Solution);
                        samestep = true;
                    }
                    else
                    {
                        // AnimateExistingFactEvent.Invoke(_new, FactWrapper.FactMaterials.Hint); // Automaticly done in FactRecorder
                        old_to_new.Add(new_fact.Id, added.Id);
                    }
                }

                //yield return null;
            }

            Debug.Log($"Facts parsed within {(System.DateTime.UtcNow - parseTime).TotalMilliseconds}ms");
            yield break;
        }
    }

    public void NewAssignmentTrigger()
    {
        //return; //if you read this, delete this line!

        if (ActiveScroll?.ScrollReference == null
         || NoDynamicScroll.Contains(ActiveScroll.ScrollReference))
            return;

        if (AutomaticHintGenerationActive || DynamicScrollDescriptionsActive)
            StartCoroutine(_NewAssignment());

        IEnumerator _NewAssignment()
        {
            if (DynamicScrollInQue)
                yield break; // only need next in que to finish

            DynamicScrollInQue = true;
            while (!SendingViewDone)
                yield return null; // if we dont wait => server will crash
            DynamicScrollInQue = false;

            yield return SendView("/scroll/dynamic");

            if (currentMmtAnswer == null)
            {
                Debug.LogError("Dynamic Scroll FAILED");
            }
            else
            {
                ScrollDynamicInfo scrollDynamicInfo;
                try
                {
                    scrollDynamicInfo = JsonConvert.DeserializeObject<ScrollDynamicInfo>(currentMmtAnswer);
                    //scrollDynamicInfo = IJSONsavable<ScrollDynamicInfo>.postprocess(scrollDynamicInfo); // DON'T! will remove hints
                }
                catch (JsonSerializationException ex)
                {
                    Debug.LogException(ex);
                    Debug.LogError("Could not Deserialize MMT aswer for /scroll/dynamic\n" + currentMmtAnswer);
                    yield break;
                }

                ScrollApplicationCheckingError[] errors = scrollDynamicInfo.errors
                    .Where(err => err.kind != "nonTotal") // expected
                    .ToArray();

                if (errors.Length > 0)
                    ScrollApplicationCheckingErrorEvent.Invoke(errors);

                processScrollDynamicInfo(scrollDynamicInfo);
            }
        }
    }

    private IEnumerator SendView(string endpoint)
    {
        SendingViewDone = false;

        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.LogWarning(www.error);
            currentMmtAnswer = null;
        }
        else
        {
            while (!www.downloadHandler.isDone)
                yield return null;

            currentMmtAnswer = www.downloadHandler.text
                .Replace("\"float\":null", "\"float\":0.0"); // cannot convert null to value type
        }

        SendingViewDone = true;
        yield break;

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

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

            return JsonConvert.SerializeObject(new ScrollApplication(ActiveScroll.ScrollReference, assignmentList));
        }
    }

    private void processScrollDynamicInfo(ScrollDynamicInfo scrollDynamicInfo)
    {
        //TODO: more hints available in scrollDynamicInfo.rendered.requiredFacts

        LatestBackwartsCompletions = scrollDynamicInfo.backward_completions.Count > 0
            ? scrollDynamicInfo.backward_completions[0]
            : new List<ScrollAssignment>();

        List<string> hintUris = LatestBackwartsCompletions
            .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 (RenderedScrollFact == null)
                { // e.g. FUNCs
                    //if(CommunicationEvents.VerboseScroll)
                    //    Debug.Log($"Descrapancy between requiredFacts and displayed facts:" +
                    //        $"Could not find display with ref: {rendered.requiredFacts[i].@ref.uri}");
                    continue;
                }

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

                //If ScrollFact is assigned -> No Hint
                if (!RenderedScrollFact.IsSet)
                {
                    List<Fact> HintFactList = Fact.MMTFactory(rendered.requiredFacts[i]);

                    foreach (Fact HintFact in HintFactList)
                    {
                        hintUris.Add(HintFact.Id); // == rendered.requiredFacts[i].@ref.uri
                        LatestRenderedHints.Add(HintFact);
                    }
                }
            }

            return;
        }
    }

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

        Fact hintFact = LatestRenderedHints.Find(x => x.Id == scrollParameterUri); // "Dictionary"

        ScrollAssignment suitableCompletion =
            LatestBackwartsCompletions.Find((ScrollAssignment x) => x.fact.uri == scrollParameterUri); // "Dictionary"

        if (suitableCompletion != null && suitableCompletion.assignment is OMS assignment)
        {
            if (FactRecorder.AllFacts.ContainsKey(assignment.uri))
            {
                AnimateExistingFactEvent.Invoke(
                    assignment.uri,
                    FactWrapper.FactMaterials.Hint
                );
            }
        }
        else if (hintFact != null)
        {
            if (FactRecorder.FindEquivalent(StageStatic.stage.factState.MyFactSpace, hintFact, out string found_key, out Fact _, out bool _, false))
                // existing fact -> Animate that
                AnimateExistingFactEvent.Invoke(
                    found_key,
                    FactWrapper.FactMaterials.Hint
                );

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