using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
using System.Linq;
using System;
using UnityEngine.InputSystem.Utilities;
using static CommunicationEvents;

//TODO: MMT: move some functionality there
//TODO: consequent!= samestep != dependent
//TODO: support renamne functionality

/// <summary>
/// Organizes (insertion/ deletion / etc. operations) and sepperates <see cref="Fact">Fact</see> spaces.
/// Keeps track of insertion/ deletion actions for <see cref="undo"/> and <see cref="redo"/>.
/// </summary>
public class FactOrganizer : IJSONsavable<FactOrganizer>, IDisposable
{
    /// <summary>
    /// Contains Immutable <see cref="Fact.Id"/>s; 
    /// e.g: From a <see cref="SolutionOrganizer"/> which are being exposed to the Player.
    /// <remarks> Will NOT be recorded in <see cref="Workflow"/>!</remarks>
    /// </summary>
    [JsonProperty]
    protected List<string> ImmutableFacts = new();

    /// <summary>
    /// - <c>Key</c>: <see cref="Gadget"/> Used Gadget
    /// - <c>Value</c>: <see cref="(int first_occurrence, int last_occurrence)"/> First and Last occurence of gadget in Workflow
    /// </summary>
    protected Dictionary<Gadget, (int first_occurrence, int last_occurrence)> GadgetWorkflowDict = new();

    /// <summary>
    /// - <c>Key</c>: <see cref="int"/> First occurence of gadget in Workflow
    /// - <c>Value</c>: <see cref="Gadget"/> Used Gadget
    /// </summary>
    [JsonProperty]
    protected Dictionary<int, Gadget> WorkflowGadgetDict = new() { { -1, null } };

    /// <summary>
    /// - <c>Key</c>: <see cref="Fact.Id"/>
    /// - <c>Value</c>: <see cref="Fact"/>
    /// </summary>
    [JsonIgnore]
    protected IReadOnlyDictionary<string, Fact> MyFactSpace
    {
        get => GlobalFactDictionary.MyFactSpace(this);
    }

    [JsonProperty]
    private IReadOnlyDictionary<string, Fact> JsonFactSpace = null; //null!

    /// <summary>
    /// - <c>Key</c>: <see cref="Fact.Id"/>
    /// - <c>Value</c>: <see cref="Fact"/>
    /// </summary>
    public static IReadOnlyDictionary<string, Fact> AllFacts { get => GlobalFactDictionary.AllFacts; }

    /// <summary>
    /// - <c>Key</c>: <see cref="Fact.Id"/>
    /// - <c>Value</c>: <see cref="meta"/>
    /// </summary>
    [JsonProperty]
    protected Dictionary<string, meta> MetaInf = new();

    /// <summary>
    /// Keeps track of insertion/ deletion/ etc. operations for <see cref="undo"/> and <see cref="redo"/>
    /// </summary>
    [JsonProperty]
    protected List<stepnote> Workflow = new();

    /// <summary>
    /// Notes position in <see cref="Workflow"/> for <see cref="undo"/> and <see cref="redo"/>; the pointed to element is non-acitve
    /// </summary>
    [JsonProperty]
    protected int marker = 0;

    /// <summary>
    /// Backlock logic redundant - for convinience.
    /// Keeps track of number of steps in <see cref="Workflow"/>.
    /// One step can consist of multiple operations.
    /// <seealso cref="stepnote"/>
    /// </summary>
    [JsonProperty]
    protected int worksteps = 0;
    /// <summary>
    /// Backlock logic redundant - for convinience.
    /// Keeps track of number of steps in <see cref="Workflow"/>, which are not set active.
    /// One step can consist of multiple operations.
    /// <seealso cref="stepnote"/>
    /// </summary>
    [JsonProperty]
    protected int backlog = 0;

    /// <summary>
    /// Set to <c>true</c> if recently been resetted.
    /// </summary>
    [JsonProperty]
    protected bool soft_resetted = false;

    /// <summary>
    /// If set to <c>true</c>, <see cref="Remove(string, bool)"/> and <see cref="Add(Fact, out bool, bool)"/> will invoke <see cref="CommunicationEvents.RemoveFactEvent"/> and <see cref="CommunicationEvents.AddFactEvent"/> respectively.
    /// </summary>
    [JsonProperty]
    public bool invoke;

    // TODO? SE: better seperation
    /// <summary>
    /// Keeps track of maximum <see cref="Fact.LabelId"/> for <see cref="Fact.generateLabel"/>.
    /// </summary>
    [JsonProperty]
    protected internal int MaxLabelId = 0;
    /// <summary>
    /// Stores unused <see cref="Fact.LabelId"/> for <see cref="Fact.generateLabel"/>, wich were freed in <see cref="Fact.freeAutoLabel"/> for later reuse to keep naming space compact.
    /// </summary>
    [JsonProperty]
    protected internal SortedSet<int> UnusedLabelIds = new();

    // TODO: put this stuff in Interface
    /// @{ <summary>
    /// For <see cref="store(string, List<Directories>, bool, bool)"/> and <see cref="load(ref FactOrganizer, bool, string, List<Directories>, bool, out Dictionary<string, string>)"/>
    /// </summary>
    public string name { get; set; } = null;
    public string path { get; set; } = null;
    /// @}


    /// <summary>
    /// Keeps track of insertion/ deletion/ etc. operations for <see cref="undo"/> and <see cref="redo"/>
    /// Used Terminology
    /// ================
    /// - steproot: elements where <see cref="samestep"/> == <c>true</c>
    /// - steptail: elements where <see cref="samestep"/> == <c>false</c>
    /// <seealso cref="Workflow"/>
    /// </summary>
    public struct stepnote
    {
        /// <summary> <see cref="Fact.Id"/> </summary>
        public string Id;

        /// <summary>
        /// <c>true</c> if this Fact has been created in the same step as the last one 
        ///      steproot[false] (=> steptail[true])*
        /// </summary>
        public bool samestep;

        /// <summary>
        /// For fast access of beginning and end of steps.
        /// Reference to position in <see cref="Workflow"/> of:
        /// - steproot: for all elements in steptail
        /// - after steptail-end: for steproot
        /// </summary>
        public int steplink;

        /// <summary> distincts creation and deletion </summary>
        public bool creation;

        /// <summary>
        /// keeps track with wich <see cref="Gadget"/> the <see cref="Fact"/> is created
        /// <c>-1</c> iff its not the case
        /// </summary>
        public int gadget_rank;

        /// <summary>
        /// keeps track with wich <see cref="Scroll"/> the <see cref="Fact"/> is created
        /// <c>null</c> iff its not the case
        /// </summary>
        public string scroll_label;

        /// <summary>Loggs <see cref="Fact"/>s used by <see cref="Gadget"/></summary>
        public string[] GadgetFlow;

        /// <summary>Loggs time, when <see cref="Fact"/> was manipulated</summary>
        public double GadgetTime;


        /// <summary>
        /// Initiator
        /// </summary>
        /// <param name="Id"><see cref="Fact.Id"/></param>
        /// <param name="samestep">sets <see cref="samestep"/></param>
        /// <param name="creation">sets <see cref="creation"/></param>
        /// <param name="that"><see cref="FactOrganizer"/> of which <c>this</c> will be added in its <see cref="FactOrganizer.Workflow"/></param>
        public stepnote(FactOrganizer that, string Id, bool samestep, bool creation, Gadget gadget, string scroll_label)
        {
            this.Id = Id;
            this.samestep = samestep;
            this.creation = creation;
            this.scroll_label = scroll_label;
            this.GadgetTime = StageStatic.stage_time;

            stepnote prev = default;

            if (samestep)
            {
                prev = that.Workflow[that.marker - 1];
                // steplink = !first_steptail ? previous.steplink : steproot
                this.steplink = prev.samestep ? prev.steplink : that.marker - 1;
            }
            else
                // steproot sets steplink after itself (end of steptail)
                this.steplink = that.marker + 1;

            this.GadgetFlow = new string[0];
            this.gadget_rank = -1;
            if (gadget != null)
            {
                bool new_gadget;
                if (new_gadget = !that.GadgetWorkflowDict.ContainsKey(gadget))
                {
                    that.GadgetWorkflowDict.Add(gadget, (that.marker, that.marker));

                    that.WorkflowGadgetDict.Remove(that.marker);
                    that.WorkflowGadgetDict.Add(that.marker, gadget);
                }
                var (gadget_first_occurrence, gadget_last_occurrence) = that.GadgetWorkflowDict[gadget];

                this.gadget_rank = gadget_first_occurrence;

                // check for new USE of gadget
                bool set_workflow = !samestep || new_gadget || prev.gadget_rank != this.gadget_rank
                  || gadget_last_occurrence >= that.marker;

                stepnote gadget_prev = set_workflow 
                    ? default /*unused then*/
                    : that.Workflow[gadget_last_occurrence];

                if (set_workflow || gadget_prev.GadgetFlow == null
                  || !gadget_prev.GadgetFlow.SequenceEqual(gadget.Workflow.ToArray()))
                {
                    this.GadgetFlow = gadget.Workflow.ToArray();
                    that.GadgetWorkflowDict[gadget] = (gadget_first_occurrence, that.marker);
                }
            }
        }
    }

    /// <summary>
    /// Each <see cref="Fact"/> entry in <see cref="MyFactSpace"/> has a corresponding <see cref="meta"/> entry in <see cref="MetaInf"/>.
    /// The <see cref="meta"/> struct is a collection of %meta-variables.
    /// <seealsocref="PruneWorkflow"/>
    /// </summary>
    public struct meta
    {
        // TODO? -> public int last_occurence; // for safe_dependencies
        /// <summary>
        /// position of first occurrence in <see cref="Workflow"/>
        /// </summary>
        public int workflow_id;

        /// <summary>
        /// keeps track wether <see cref="Fact"/> is currently in Scene
        /// </summary>
        public bool active;

        /// <summary>
        /// if <c>true</c> <see cref="Fact"/> is imported from a <see cref="SolutionOrganizer"/>
        /// </summary>
        public bool isImmutable;

        /// <summary>
        /// Initiator
        /// </summary>
        /// <param name="workflow_id">sets <see cref="workflow_id"/></param>
        /// <param name="active">sets <see cref="active"/></param>
        /// <param name="isImmutable">sets <see cref="isImmutable"/></param>
        public meta(int workflow_id, bool active, bool isImmutable)
        {
            this.workflow_id = workflow_id;
            this.active = active;
            this.isImmutable = isImmutable;
        }
    }


    static FactOrganizer()
    {
        IJSONsavable<FactOrganizer>.hierarchie = new List<Directories> { Directories.FactStateMachines };
    }

    /// <summary>
    /// Only used by <see cref="JsonConverter"/> to initiate empty instance.
    /// </summary>
    public FactOrganizer()
    {
        GlobalFactDictionary.NewFactSpace(this);
        this.invoke = false;
    }

    ~FactOrganizer()
    {
        Dispose();
    }

    public void Dispose()
        => GlobalFactDictionary.FactSpaceDelete(this);

    /// <summary>
    /// Standard Constructor for empty, ready to use <see cref="FactOrganizer"/>
    /// </summary>
    /// <param name="invoke">sets <see cref="invoke"/>.</param>
    public FactOrganizer(bool invoke = false) : this()
    {
        this.invoke = invoke;
    }

    /// <summary>
    /// Used to parse read-in <see cref="FactOrganizer"/> by <see cref="JsonReader"/> and make <see cref="Fact.Id"/> conform.
    /// Also poppulates <see cref="OldToNewURI"/>
    /// </summary>
    /// <param name="target">to be parsed into, will be overwritten. 
    /// If <c><paramref name="invoke"/> = true</c>, <paramref name="target"/> should be <see cref="StageStatic.stage.factState"/>, outherwise <see cref="InvokeFactEvent(bool, string)"/> will cause <see cref="Exception">Exceptions</see> when it invokes Events of <see cref="CommunicationEvents"/></param>
    /// <param name="source">instance to be parsed</param>
    /// <param name="invoke">see <see cref="invoke"/></param>
    /// <param name="old_to_new">Generated to map <c>Key</c> outdated <see cref="Fact.Id"/>s to corresponding <c>Value</c> updatated <see cref="Fact.Id"/>s.</param>
    public static T ReInitializeFactOrganizer<T>(T source, bool invoke, out Dictionary<string, string>  old_to_new)
        where T : FactOrganizer, new()
    {   // TODO: other strategy needed when MMT save/load supported

        bool source_initialized = GlobalFactDictionary.AllFactSpaces.ContainsKey(source) 
            && source.MyFactSpace.Count > 0;

        Dictionary<string, string> _old_to_new = source_initialized
            ? source.MyFactSpace.Keys.ToDictionary(id => id)
            : new();

        // initiate
        T target = new()
        {
            invoke = invoke,
            MaxLabelId = source.MaxLabelId,
            UnusedLabelIds = source.UnusedLabelIds,
        };

        // work ExposedSolutionFacts
        foreach (var element in source.ImmutableFacts)
        {
            Fact ExposedFact = ReInitializeFact(element);
            if (ExposedFact == null)
                continue;

            target.Add(ExposedFact, out _, false, null, null, isImmutable: true);
        }

        // work Workflow
        for (int i = 0; i < source.Workflow.Count; i++)
        {
            stepnote s_step = source.Workflow[i];
            Gadget used_gadget = source.WorkflowGadgetDict[s_step.gadget_rank];

            if (used_gadget != null)
            {
                Gadget init_gadget = GadgetBehaviour.gadgets?.FirstOrDefault(g => Gadget.Equals(g, used_gadget));
                if (init_gadget != null && init_gadget != default(Gadget))
                    used_gadget = init_gadget;

                used_gadget.Workflow = s_step.GadgetFlow.Select(uri => _old_to_new[uri]).ToList();
            }

            if (s_step.creation)
            // Add
            {
                Fact add = ReInitializeFact(s_step.Id);
                target.Add(add, out _, s_step.samestep, used_gadget, s_step.scroll_label);
            }
            else if (_old_to_new.TryGetValue(s_step.Id, out string remove_Id))
            // Remove
            {
                target.Remove(remove_Id, s_step.samestep, used_gadget);
            }

            stepnote t_step = target.Workflow[i];
            t_step.GadgetTime = s_step.GadgetTime;
            target.Workflow[i] = t_step;
        }

        // set un-redo state
        while (target.backlog < source.backlog)
            target.undo();

        target.soft_resetted = source.soft_resetted;

        if (!source_initialized)
            source.Dispose();

        old_to_new = _old_to_new;
        return target;

        Fact ReInitializeFact(string old_id)
        {
            if (source_initialized)
                return source.MyFactSpace[old_id]; // is in GlobalFactDictionary.Facts

            if (_old_to_new.TryGetValue(old_id, out string newId))
                return target[newId];

            Fact old_Fact = source.JsonFactSpace[old_id];

            if (!old_Fact.DependentFactIds.All(id => _old_to_new.ContainsKey(id)))
            {
                Debug.LogWarningFormat("Could not Instantiate Immutable Fact: {0}\n\tTrying to compensate...", old_Fact);
                _old_to_new.Add(old_Fact.Id, old_Fact.Id);
                return null;
            }

            Fact new_Fact = old_Fact.ReInitializeMe(_old_to_new, target);

            _old_to_new.Add(old_Fact.Id, new_Fact.Id);

            return new_Fact;
        }
    }

    /// <summary>
    /// wrappes <c><see cref="MyFactSpace"/>[<paramref name="id"/>]</c>
    /// <seealso cref="ContainsKey(string)"/>
    /// </summary>
    /// <param name="id">a <see cref="Fact.Id"/> in <see cref="MyFactSpace"/></param>
    /// <returns><c><see cref="MyFactSpace"/>[<paramref name="id"/>]</c></returns>
    public Fact this[string id] { get => MyFactSpace[id]; }

    /// <summary>
    /// Exposes <see cref="Dictionary{TKey, TValue}.TryGetValue(TKey, out TValue)"/> of <see cref="MyFactSpace"/>
    /// </summary>
    /// <param name="URI"><see cref="Fact.Id"/> to search for</param>
    /// <param name="found"><see cref="Fact"/> iff found, else <c>null</c></param>
    /// <returns>wehter <see cref="Fact"/> with <see cref="Fact.Id"/> was found</returns>
    public bool TryGetFact(string URI, out Fact found)
        => MyFactSpace.TryGetValue(URI, out found);

    /// <summary>
    /// Exposes contens of <see cref="MetaInf"/>
    /// <seealso cref="ContainsKey(string)"/>
    /// </summary>
    /// <param name="id"> or URI of <see cref="Fact"/></param>
    /// <returns><see cref="meta"/> <c>struct</c> of given <see cref="Fact"/></returns>
    public meta GetFactMeta(string id) => MetaInf[id];

    /// <summary>
    /// Exposes contens of <see cref="Workflow"/>
    /// </summary>
    /// <param name="index"> to get</param>
    /// <returns><see cref="stepnote"/> of given <paramref name="index"/></returns>
    public stepnote GetWorkflow(int index) => Workflow[index];

    /// <summary>
    /// wrappes <c><see cref="MyFactSpace"/>.ContainsKey(<paramref name="id"/>)</c>
    /// </summary>
    /// <param name="id">a <see cref="Fact.Id"/></param>
    /// <returns><c><see cref="MyFactSpace"/>.ContainsKey(<paramref name="id"/>)</c></returns>
    public bool ContainsKey(string id) => MyFactSpace.ContainsKey(id);

    /// <summary>
    /// Looks up if there is a <paramref name="label"/> <see cref="Fact.Label"/> in <see cref="MyFactSpace"/>.Values
    /// </summary>
    /// <param name="label">supposed <see cref="Fact.Label"/> to be checked</param>
    /// <returns><c>true</c> iff <see cref="MyFactSpace"/> conatains a <c>Value</c> <see cref="Fact"/>, where <see cref="Fact.Label"/> == <paramref name="label"/>.</returns>
    public bool ContainsLabel(string label)
    {
        if (string.IsNullOrEmpty(label))
            return false;

        var hit = MyFactSpace.FirstOrDefault(e => e.Value.Label == label);
        return !hit.Equals(default);
    }

    //TODO? MMT? PERF: O(n), every Fact-insertion
    /// <summary>
    /// Looks for existent <see cref="Fact"/> (<paramref name="found"/>) which is very similar or identical (<paramref name="exact"/>) to prposed <see cref="Fact"/> (<paramref name="search"/>)
    /// <remarks>does not check active state</remarks>
    /// </summary>
    /// <param name="search">to be searched for</param>
    /// <param name="found"><see cref="Fact.Id"/> if return value is <c>true</c></param>
    /// <param name="exact"><c>true</c> iff <paramref name="found"/> == <paramref name="search"/><see cref="Fact.Id">.Id</see></param>
    /// <returns><c>true</c> iff the exact same or an equivalent <see cref="Fact"/> to <paramref name="search"/> was found in <see cref="MyFactSpace"/></returns>
    private bool FindEquivalent(Fact search, out string found, out bool exact)
    {
        if (exact = MyFactSpace.ContainsKey(search.Id))
        {
            found = search.Id;
            return true;
        }

        foreach (var entry in MyFactSpace)
        {
            if (entry.Value.Equivalent(search))
            {
                found = entry.Key;
                return true;
            }
        }

        found = null;
        return false;
    }

    /// <summary>
    /// <see cref="PruneWorkflow">prunes</see> & adds <paramref name="note"/> to <see cref="Workflow"/>; <see cref="InvokeFactEvent(bool, string)">Invokes Events</see>
    /// </summary>
    /// <param name="note">to be added</param>
    private void WorkflowAdd(stepnote note)
    {
        PruneWorkflow(note);

        if (note.samestep)
        // update steplink of steproot
        {
            stepnote tmp = Workflow[note.steplink];
            tmp.steplink = Workflow.Count + 1;
            Workflow[note.steplink] = tmp;
        }
        else
            worksteps++;

        Workflow.Add(note);
        marker = Workflow.Count;

        InvokeFactEvent(note.creation, note.Id);
    }

    /// <summary>
    /// set current (displayed) state in stone, a.k.a. <see cref="Fact.delete(bool)">delete</see> non <see cref="meta.active"/> <see cref="Fact">Facts</see> for good;
    /// resets <see cref="undo">un</see>-<see cref="redo"/> parameters
    /// </summary>
    private void PruneWorkflow(stepnote not_me)
    {
        /*if (soft_resetted)
            this.hardreset(false); // musn't clear

        else*/
        if (backlog > 0)
        {
            worksteps -= backlog;
            backlog = 0;

            for (int i = Workflow.Count - 1; i >= marker; i--)
            // cleanup now obsolete Facts
            {
                stepnote last = Workflow[i];

                if (last.gadget_rank == MetaInf[last.Id].workflow_id
                 && last.gadget_rank != not_me.gadget_rank)
                {   // Remove Gadget, if its the first time it's beeing used
                    GadgetWorkflowDict.Remove(WorkflowGadgetDict[last.gadget_rank]);
                    WorkflowGadgetDict.Remove(last.gadget_rank);
                }

                if (last.Id != not_me.Id // may be zombie
                 && MetaInf[last.Id].workflow_id == i)
                // remove for good, if original creation gets pruned
                {
                    GlobalFactDictionary.FactSpaceRemove(this, last.Id);
                    MetaInf.Remove(last.Id);
                }
            }

            // prune Worklfow down to marker
            Workflow.RemoveRange(marker, Workflow.Count - marker);
        }
    }

    /// <summary>
    /// Call this to Add a <see cref="Fact"/> to <see cref="FactOrganizer">this</see> instance.
    /// <remarks>*Warning*: If return_value != <paramref name="value"/><see cref="Fact.Id">.Id</see>, <paramref name="value"/> will be <see cref="Fact.delete(bool)">deleted</see> for good to reduce ressource usage!</remarks>
    /// </summary>
    /// <param name="value">to be added</param>
    /// <param name="exists"><c>true</c> iff <paramref name="value"/> already exists (may be <see cref="meta.active">inactive</see> before opreation)</param>
    /// <param name="samestep">set <c>true</c> if <see cref="Fact"/> creation happens as a subsequent/ consequent step of multiple <see cref="Fact"/> creations and/or deletions, 
    /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param>
    /// <param name="gadget">the <see cref="Gadget"/>used to create this <see cref="Fact"/> or <c>null</c></param>
    /// <param name="scroll_label">the <see cref="Scroll"/>used to create this <see cref="Fact"/> or <c>null</c></param>
    /// <param name="isImmutable">will eneable delete protection, will BE recorded in <see cref="ImmutableFacts"/>, will NOT be recorded in <see cref="Workflow"/></param>
    /// <returns><see cref="Fact.Id"/> of <paramref name="value"/> or <see cref="FindEquivalent(Fact, out string, out bool)">found</see> <see cref="Fact"/> iff <paramref name="exists"/>==<c>true</c></returns>
    public string Add(Fact value, out bool exists, bool samestep, Gadget gadget, string scroll_label, bool isImmutable = false)
    {
        soft_resetted = false;
#pragma warning disable IDE0018 // Inlinevariablendeklaration
        string key;
#pragma warning restore IDE0018 // Inlinevariablendeklaration

        if (exists = FindEquivalent(value, out key, out bool _))
        {
            if (exists = MetaInf[key].active) //Fact in Scene?
                // desired outcome already achieved
                return key;

            if (MetaInf[key].workflow_id > marker)
                // update meta data: everything >= marker will be pruned (except this Fact)
                MetaInf[key] = new meta(marker, true, isImmutable);
        }
        else
        // brand new Fact
        {
            key = value.Id;
            GlobalFactDictionary.FactSpaceAdd(this, value);
            MetaInf.Add(key, new meta(marker, true, isImmutable));
        }

        if (isImmutable)
        {
            ImmutableFacts.Add(key);
            InvokeFactEvent(true, key);
        }
        else
            WorkflowAdd(new stepnote(this, key, samestep, true, gadget, scroll_label));

        return key;
    }

    /// <summary>
    /// Call this to Remove a <see cref="Fact"/> from <see cref="FactOrganizer">this</see> instance.
    /// If other <see cref="Fact">Facts</see> depend on <paramref name="value"/> <see cref="Remove(Fact, bool)">Remove(/<depending Fact/>, <c>true</c>)</see> will be called recursively/ cascadingly.
    /// </summary>
    /// <remarks>this will not <see cref="Fact.delete(bool)">delete</see> a <see cref="Fact"/>, but sets it <see cref="meta.active">inactive</see> for later <see cref="Fact.delete(bool)">deletion</see> when <see cref="PruneWorkflow">pruned</see>.</remarks>
    /// <param name="value">to be removed</param>
    /// <param name="samestep">set <c>true</c> if <see cref="Fact"/> deletion happens as a subsequent/ consequent step of multiple <see cref="Fact"/> creations and/or deletions, 
    /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param>
    /// <param name="gadget">the <see cref="Gadget"/>used to delete <paramref name="value"/> or <c>null</c></param>
    /// <param name="deleteSolutionFact">can overwrite protection from <see cref="Fact"/>s imported from a <see cref="SolutionOrganizer"/></param>
    /// <returns><c>true</c> iff <paramref name="value"/><see cref="Fact.Id">.Id</see> was found.</returns>
    public bool Remove(Fact value, bool samestep, Gadget gadget, bool deleteSolutionFact = false)
        => this.Remove(value.Id, samestep, gadget, deleteSolutionFact);

    /// \copybrief Remove(Fact, bool)
    /// <remarks>this will not <see cref="Fact.delete(bool)">delete</see> a <see cref="Fact"/>, but sets it <see cref="meta.active">inactive</see> for later <see cref="Fact.delete(bool)">deletion</see> when <see cref="PruneWorkflow">pruned</see>.</remarks>
    /// <param name="key">to be removed</param>
    /// <param name="samestep">set <c>true</c> if <see cref="Fact"/> deletion happens as a subsequent/ consequent step of multiple <see cref="Fact"/> creations and/or deletions, 
    /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param>
    /// <param name="gadget">the <see cref="Gadget"/>used to delete <paramref name="value"/> or <c>null</c></param>
    /// <param name="deleteImmutables">can overwrite protection from <see cref="Fact"/>s in <see cref="ImmutableFacts"/></param>
    /// <returns><c>true</c> iff <paramref name="value"/> was found.</returns>
    public bool Remove(string key, bool samestep, Gadget gadget, bool deleteImmutables = false)
    //no reset check needed (impossible state)
    {
        if (!MyFactSpace.ContainsKey(key))
            return false;

        if (!deleteImmutables && MetaInf[key].isImmutable)
            // may not be deleted
            return false;

        if (!MetaInf[key].active)
            // desiered outcome reality
            return true;

        //TODO: see issue #58

        safe_dependencies(key, out List<string> deletethis);

        if (deletethis.Count > 0)
        {
            yeetusdeletus(deletethis, samestep, gadget);
        }

        return true;
    }

    // TODO: MMT: decide dependencies there (remember virtual deletions in Unity (un-redo)!)
    // TODO? decrease runtime from amorised? O((n/2)^2)
    /// <summary>
    /// searches recursively for <see cref="Fact">Facts</see> where <see cref="Fact.getDependentFactIds"/> includes <paramref name="key"/>/ found dependencies
    /// </summary>
    /// <param name="key">to be cross referenced</param>
    /// <param name="dependencies">all <see cref="Fact">Facts</see> where <see cref="Fact.getDependentFactIds"/> includes <paramref name="key"/>/ found dependencies</param>
    /// <returns><c>false</c> if any dependencies are <see cref="stepnote">steproots</see></returns>
    public bool safe_dependencies(string key, out List<string> dependencies)
    {
        dependencies = new List<string>();
        int c_unsafe = 0;

        int pos = MetaInf[key].workflow_id;
        dependencies.Add(key);

        // accumulate facts that are dependent of dependencies
        for (int i = pos; i < marker; i++)
        {
            // TODO: consequent != samestep != dependent (want !consequent)
            if (!Workflow[i].creation && Workflow[i].Id != key)
            {
                // just try
                if (dependencies.Remove(Workflow[i].Id) && !Workflow[i].samestep)
                    c_unsafe--;
            }
            else if (this[Workflow[i].Id].DependentFactIds.Intersect(dependencies).Any() && Workflow[i].Id != key)
            {
                dependencies.Add(Workflow[i].Id);
                if (!Workflow[i].samestep)
                    c_unsafe++;
            }
        }

        return c_unsafe == 0;
    }

    /// <summary>
    /// Turns every <see cref="Fact"/> in <paramref name="deletereverse"/> (in reverse order) <see cref="meta.active">inactive</see>, as it would be <see cref="Remove(string, bool)">removed</see>, but without checking for (recursive) dependencies.
    /// </summary>
    /// <param name="deletereverse">to be <see cref="Remove(string, bool)">removed</see>, but without checking for (recursive) dependencies</param>
    /// <param name="samestep">see <see cref="Remove(string, bool).samestep"/>. Only applies to last (first iteration) element of <paramref name="deletereverse"/>; for everything else <paramref name="samestep"/> will be set to <c>true</c>.</param>
    private void yeetusdeletus(List<string> deletereverse, bool samestep, Gadget gadget)
    {
        for (int i = deletereverse.Count - 1; i >= 0; i--, samestep = true)
        {
            WorkflowAdd(new stepnote(this, deletereverse[i], samestep, false, gadget, null));
        }
    }

    /// <summary>
    /// reverses any entire step; adds process to Workflow!
    /// </summary>
    /// <remarks>*Warning*: unused therefore untested and unmaintained.</remarks>
    /// <param name="pos">position after <see cref="stepnote">steptail-end</see> of the step to be reversed</param>
    /// <param name="samestep">see <see cref="yeetusdeletus(List<string>, bool).samestep"/></param>
    private void reversestep(int pos, bool samestep = false)
    {
        pos--;

        if (pos >= marker)
            // check for valid step (implicit reset check)
            return;

        for (int i = pos, stop = Workflow[pos].samestep ? Workflow[pos].steplink : pos;
            i >= stop; i--, samestep = true)
        {
            if (Workflow[i].creation)
                Remove(Workflow[i].Id, samestep, null);
            else if (!MetaInf[Workflow[i].Id].active)
                WorkflowAdd(new stepnote(this, Workflow[i].Id, samestep, true, null, null));
        }
    }

    /// <summary>
    /// Undoes an entire <see cref="stepnote">step</see> or last <see cref="softreset"/> .
    /// No <see cref="Fact"/> will be actually <see cref="Add(Fact, out bool, bool)">added</see>, <see cref="Remove(Fact, bool)">removed</see> or <see cref="Fact.delete(bool)">deleted</see>; only its visablity and <see cref="meta.active"/> changes.
    /// <seealso cref="marker"/>
    /// <seealso cref="worksteps"/>
    /// <seealso cref="backlog"/>
    /// </summary>
    public void undo()
    {
        if (soft_resetted)
            fastforward(); // revert softreset

        else if (backlog < worksteps)
        {
            backlog++;

            stepnote last = Workflow[--marker];
            int stop = last.samestep ? last.steplink : marker;
            for (int i = marker; i >= stop; i--)
            {
                last = Workflow[i];
                InvokeFactEvent(!last.creation, last.Id);
            }

            marker = stop;
        }
    }

    /// <summary>
    /// Redoes an entire <see cref="stepnote">step</see> .
    /// No <see cref="Fact"/> will be actually <see cref="Add(Fact, out bool, bool)">added</see>, <see cref="Remove(Fact, bool)">removed</see> or <see cref="Fact.delete(bool)">deleted</see>; only its visablity and <see cref="meta.active"/> changes.
    /// <seealso cref="marker"/>
    /// <seealso cref="worksteps"/>
    /// <seealso cref="backlog"/>
    /// </summary>
    public void redo()
    {
        soft_resetted = false;

        if (backlog > 0)
        {
            backlog--;

            stepnote last = Workflow[marker];
            int stop = last.samestep ? Workflow[last.steplink].steplink : last.steplink;
            for (int i = marker; i < stop; i++)
            {
                last = Workflow[i];
                InvokeFactEvent(last.creation, last.Id);
            }

            marker = stop;
        }
    }

    /// <summary>
    /// Resets to "factory conditions".
    /// Neither <see cref="Fact.delete(bool)">deletes</see> <see cref="Fact">Facts</see> nor invokes <see cref="CommunicationEvents.RemoveFactEvent"/>
    /// </summary>
    /// <seealso cref="hardreset(bool)"/>
    public void Clear()
    {
        GlobalFactDictionary.FactSpaceClear(this);
        MetaInf.Clear();
        Workflow.Clear();
        ImmutableFacts.Clear();
        GadgetWorkflowDict.Clear();

        marker = 0;
        worksteps = 0;
        backlog = 0;
        soft_resetted = false;
    }

    /// <summary>
    /// Resets to "factory conditions".
    /// <see cref="Fact.delete(bool)">deletes</see> <see cref="Fact">Facts</see> and invokes <see cref="CommunicationEvents.RemoveFactEvent"/> iff <paramref name="invoke_event"/> && <see cref="invoke"/>.
    /// </summary>
    /// <seealso cref="Clear"/>
    /// <param name="invoke_event">if set to <c>true</c> *and* <see cref="invoke"/> set to <c>true</c> will invoke <see cref="CommunicationEvents.RemoveFactEvent"/></param>
    public void hardreset(bool invoke_event = true)
    {
        if (invoke_event && invoke)
            foreach (var entry in MyFactSpace)
                if (MetaInf[entry.Key].active)
                    CommunicationEvents.RemoveFactEvent.Invoke(entry.Value);

        this.Clear();
    }

    /// <summary>
    /// <see cref="undo">Undoes</see> *all* <see cref="worksteps"/> (since <see cref="marker"/>) and sets <see cref="soft_resetted"/> to <c>true</c>.
    /// </summary>
    public void softreset()
    {
        if (soft_resetted)
        {
            fastforward();
            return;
        }

        while (marker > 0)
            undo();
        // marker = 0; backlog = worksteps;

        soft_resetted = true;
    }

    /// <summary>
    /// <see cref="redo">Redoes</see> *all* <see cref="worksteps"/> (from <see cref="marker"/> onwards) and sets <see cref="soft_resetted"/> to <c>false</c>.
    /// </summary>
    public void fastforward()
    {
        while (backlog > 0)
            // also sets resetted = false;
            redo();
    }

    FactOrganizer IJSONsavable<FactOrganizer>._IJPostProcess(FactOrganizer raw_payload)
        => raw_payload == null ? raw_payload : ReInitializeFactOrganizer<FactOrganizer>(raw_payload, false, out _);

    FactOrganizer IJSONsavable<FactOrganizer>._IJPreProcess(FactOrganizer payload)
    {
        if (GlobalFactDictionary.AllFactSpaces.ContainsKey(payload) && payload.MyFactSpace.Count > 0)
            payload.JsonFactSpace = payload.MyFactSpace;
        return payload;
    }

    /// <summary>
    /// Call this after assigning a stored instance in an empty world, that was not drawn.
    /// <see cref="redo">Redoes</see>/ draws everything from <see cref="marker"/> = 0 to <paramref name="draw_all"/><c> ? worksteps : backlog</c>
    /// </summary>
    /// <remarks>Does not invoke <see cref="softreset"/> or <see cref="undo"/> in any way and thus may trigger <see cref="Exception">Exceptions</see> or undefined behaviour if any <see cref="Fact"/> in <see cref="MyFactSpace"/> is already drawn.</remarks>
    public void Draw(bool draw_all = false)
    {
        // TODO: see issue #58
        // TODO: communication with MMT

        foreach (var key in ImmutableFacts)
            InvokeFactEvent(true, key); // sets info.active = true

        marker = 0;
        var stop = draw_all ? worksteps : backlog;
        backlog = worksteps;

        while (backlog > stop)
            redo(); // resets info.active
    }

    /// <summary>
    /// Undraws everything by invoking <see cref="CommunicationEvents.RemoveFactEvent"/>, that is <see cref="meta.active"/>, but does not change that satus.
    /// </summary>
    /// <param name="force_invoke">if set <c>true</c>, invokes <see cref="CommunicationEvents.RemoveFactEvent"/> for every <see cref="Fact"/> regardles of <see cref="meta.active"/> status or <see cref="invoke"/></param>
    public void Undraw(bool force_invoke = false)
    {
        foreach (var entry in MyFactSpace)
        {
            if (force_invoke || (invoke && MetaInf[entry.Key].active))
                CommunicationEvents.RemoveFactEvent.Invoke(entry.Value);
        }
    }

    /// <summary>
    /// Updates <see cref="MetaInf"/>, <see cref="Fact.Label"/> and invokes <see cref="CommunicationEvents"/> (latter iff <see cref="invoke"/> is set)
    /// </summary>
    /// <param name="creation">wether <see cref="Fact"/> is created or removed</param>
    /// <param name="Id"><see cref="Fact.Id"/></param>
    private void InvokeFactEvent(bool creation, string Id)
    {
        // update meta struct
        meta info = MetaInf[Id];
        info.active = creation;
        MetaInf[Id] = info;

        if (invoke)
            if (creation)
                CommunicationEvents.AddFactEvent.Invoke(this[Id]);
            else
                CommunicationEvents.RemoveFactEvent.Invoke(this[Id]);

        if (creation)
            // undo freeLabel()
            _ = MyFactSpace[Id].Label;
        else
            MyFactSpace[Id].freeAutoLabel();
    }

    /// <summary>
    /// Used to check wether <see cref="FactOrganizer">this</see> satisfies the constrains of an <see cref="SolutionOrganizer">Solution</see>.
    /// Only <see cref="meta.active"/> are accounted for.
    /// </summary>
    /// <param name="MinimalSolution">describes constrains</param>
    /// <param name="MissingElements">elements which were *not* found in <see cref="SolutionOrganizer.ValidationSet"/> in a format reflecting that of <see cref="SolutionOrganizer.ValidationSet"/></param>
    /// <param name="Solutions">elements which *were* found in <see cref="SolutionOrganizer.ValidationSet"/> in a format reflecting that of <see cref="SolutionOrganizer.ValidationSet"/></param>
    /// <returns><c>true</c> iff *all* constrains set by <paramref name="MinimalSolution"/> are met</returns>
    public bool DynamiclySolved(
        SolutionOrganizer MinimalSolution,
        out List<List<string>> MissingElements,
        out List<List<string>> Solutions)
    {
        MissingElements = new List<List<string>>();
        // need to work not on ref/out
        List<List<string>> Solution_L = new();

        int MissingElementsCount = 0;
        var activeList = MyFactSpace.Values.Where(f => MetaInf[f.Id].active);

        foreach (var ValidationSet in MinimalSolution.ValidationSet)
        {
            // List to relato to. Either all active facts or those defined in RelationIndex if not empty
            var relateList = ValidationSet.RelationIndex.Count == 0 ? activeList :
                ValidationSet.RelationIndex.Select(i => Solution_L[i]) // Select by Index
                .SelectMany(i => i) // Flatten structure
                .Select(URI => this[URI]); // Get Facts

            // check by MasterIds
            // ALL Masters must relate
            var part_minimal =
                ValidationSet.MasterIDs.Select(URI => MinimalSolution[URI]);

            var part_solution =
                relateList.Where(active => part_minimal.Contains(active, ValidationSet.Comparer.SetSearchRight()))
                .ToList(); // needed for some reason

            var part_missing =
                part_minimal.Except(part_solution, ValidationSet.Comparer.SetSearchLeft());

            // SolutionIndex may include current index
            Solution_L.Add(part_solution.Select(fact => fact.Id).ToList());
            MissingElements.Add(part_missing.Select(fact => fact.Id).ToList());
            MissingElementsCount += part_missing.Count();

            // check by previous solutions
            // at least ONE member must relate
            var part_consequential_minimal =
                ValidationSet.SolutionIndex.Select(i => Solution_L[i]) // Select by Index
                .SelectMany(i => i) // Flatten structure
                .Select(URI => this[URI]); // Get Facts

            var part_consequential_solution =
                relateList.Where(active => part_consequential_minimal.Contains(active, ValidationSet.Comparer.SetSearchRight()));

            Solution_L.Last().AddRange(part_consequential_solution.Select(fact => fact.Id).ToList());
            MissingElementsCount += Convert.ToInt32(
                part_consequential_solution.Count() == 0 && part_consequential_minimal.Count() != 0);
        }

        Solutions = Solution_L;
        return MissingElementsCount == 0;
    }

    public IEnumerable<Gadget> GetUsedGadgets() => GadgetWorkflowDict.Keys;

    public int GetNumberOfGadgets() => GadgetWorkflowDict.Count;

    public IEnumerable<string> GetUsedScrolls()
        => Workflow.Where(sn => MetaInf[sn.Id].active && sn.scroll_label != null).Select(sn => sn.scroll_label).Distinct();

    public int GetNumberOfScrolls() => GetUsedScrolls().Count();

    public int GetNumberOfFacts() => MyFactSpace.Count;


    protected static class GlobalFactDictionary
    {
        /// <summary>
        /// - <c>Key</c>: <see cref="Fact.Id"/>
        /// - <c>Value</c>: <see cref="Fact"/>
        /// </summary>
        private static readonly Dictionary<string, Fact> FactDict = new();
        public static IReadOnlyDictionary<string, Fact> AllFacts { get => FactDict; }

        private static readonly Dictionary<string, uint> FactReferences = new();

        private static readonly Dictionary<FactOrganizer, Dictionary<string, Fact>> FactSpaces = new();
        public static IReadOnlyDictionary<FactOrganizer, Dictionary<string, Fact>> AllFactSpaces => FactSpaces;

        public static IReadOnlyDictionary<string, Fact> MyFactSpace(FactOrganizer me)
            => FactSpaces[me];

        public static void NewFactSpace(FactOrganizer me)
        {
            FactSpaces.Add(me, new());
        }

        public static void FactSpaceClear(FactOrganizer me)
        {
            if (!FactSpaces.ContainsKey(me))
                return;

            foreach (string key in FactSpaces[me].Keys.ToArray())
                FactSpaceRemove(me, key);
        }

        public static void FactSpaceDelete(FactOrganizer me)
        {
            FactSpaceClear(me);
            FactSpaces.Remove(me);
        }

        public static void FactSpaceAdd(FactOrganizer me, Fact fact)
        {
            if (!FactDict.ContainsKey(fact.Id))
            {
                FactDict.Add(fact.Id, fact);
                FactReferences.Add(fact.Id, 1);
            }
            else
                FactReferences[fact.Id]++;

            if (!FactSpaces.ContainsKey(me))
                FactSpaces.Add(me, new());

            FactSpaces[me].Add(fact.Id, fact);
        }

        public static void FactSpaceRemove(FactOrganizer me, string key)
        {
            FactSpaces[me].Remove(key);

            if (FactReferences[key] == 1)
            {
                FactDict.Remove(key);
                FactReferences.Remove(key);
            }
            else
                FactReferences[key]--;
        }
    }
}