using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public static class CommunicationEvents
{
    public static UnityEvent<RaycastHit[]> TriggerEvent = new();
    public static UnityEvent<RaycastHit[]> TriggerModFireEvent = new();

    public static UnityEvent<int> ToolModeChangedEvent = new();
    public static UnityEvent<Fact> AddFactEvent = new();
    public static UnityEvent<Fact> RemoveFactEvent = new();

    public static UnityEvent gameSucceededEvent = new();
    public static UnityEvent gameNotSucceededEvent = new();
    public static UnityEvent StartT0Event = new();
    /** <summary>Inform ui that it should animate the given fact with the given material.</summary> */
    public static UnityEvent<string, FactWrapper.FactMaterials> AnimateExistingFactEvent = new();
    /** <summary>Start firework and invoke AnimateExistingFactEvent.</summary> */
    public static UnityEvent<Fact, FactWrapper.FactMaterials> AnimateExistingAsSolutionEvent = new();
    /** <summary>Show a missing fact for a short time as hint for the user.</summary> */
    public static UnityEvent<Fact> AnimateNonExistingFactEvent = new();


    //------------------------------------------------------------------------------------
    //-------------------------------Global Variables-------------------------------------
    // TODO! move to GlobalStatic/Behaviour

    public static bool ServerAutoStart = true;
    public static bool ServerRunning = true;

    //CHANGE HERE PORT OF SERVER
    public static readonly string ServerPortDefault = "8085"; //used for Local
    public static readonly string ServerAddressLocal = "localhost:" + ServerPortDefault; // there is currently never a reason to change this.
    public static string ServerAdress = "http://localhost:8085"; //need "http://" //used by displayScrolls.cs //http://10.231.4.95:8085"; //IMPORTANT for MAINMENU

    public static Process process_mmt_frameIT_server;

    public static int ToolID_new;
    public static int ToolID_selected;//Script

    /// <summary>
    /// The Servers stored in NetworkJSON, as an enum to have consistent names.<br/>
    /// Why are <see cref="ServerSlot.last"/>,<see cref="ServerSlot.newIP"/> and 
    /// <see cref="ServerSlot.selecIP"/> in here? Historical Reasons^TM <br/>
    /// If one wants to optimize things, it is likely advisable to make them variables instead. 
    /// But this also requires changing the read/write of NetworkJSON, and likely more ...
    /// </summary>
    [System.Serializable]
    public enum ServerSlot
    {// The numbers are due to legacy code, specifically ServerRunningA indices
        last = 1,
        newIP = 2,
        slot1 = 3,
        slot2 = 4,
        slot3 = 5,
        selecIP = 6,
        slot4 = 7,
        slot5 = 8,
        localServer
    }

    /// <summary>
    /// The status of a <c>ServerSlot</c> in <see cref="ServerSlots"/>
    /// </summary>
    public enum ServerStatus
    {// The numbers are due to legacy code, specifically ServerRunningA values
        offline = 0,
        online = 2,
        checking = 1,
        NoNetworkAddress = 3
    }

    /// <summary>
    /// All data to a Server stored in a Server
    /// </summary>
    public class ServerSlotData
    {
        public string domain;
        public ServerStatus currentStatus = ServerStatus.offline;
        public bool hasBeenChecked = false;

        //Define constructor via expression body definition
        public ServerSlotData(string serverURL) => domain = serverURL;

        /// <summary>
        /// Ping the server to see if it is online.
        /// Start as a <c>Coroutine</c>, because this involves a <c>WebRequest</c>
        /// </summary>
        public IEnumerator UpdateServerStatus(bool skipIfAlreadyChecked = false)
        {
            if (skipIfAlreadyChecked && hasBeenChecked)
            {
                yield break; // Skip, if not neccessary
            }

            currentStatus = ServerStatus.checking;
            ServerSlot myKey = ServerSlots.FirstOrDefault(x => x.Value == this).Key;

            if (string.IsNullOrEmpty(domain))
            {
                currentStatus = ServerStatus.NoNetworkAddress;
                UnityEngine.Debug.LogWarning("Server " + myKey.ToString() + " has no network adress.");
            }
            else
            {
                UnityWebRequest request = UnityWebRequest.Get("http://" + domain + "/fact/list");
                yield return request.SendWebRequest();

                if (request.result == UnityWebRequest.Result.Success)
                {
                    currentStatus = ServerStatus.online;
                }
                else
                {
                    UnityEngine.Debug.Log("Couldn't connect to Server " + myKey.ToString() + " under " + domain + " : " + request.error + "\n");
                    currentStatus = ServerStatus.offline;

                    //try again
                    //request.Dispose();
                    //request = pingMMTServer(NetwAddress);
                    //yield return request.SendWebRequest();
                    //request.Dispose();

                }
                request.Dispose();
            }
            hasBeenChecked = true;

            // To keep ServerRunningA updated. Delete if you saved the world from that abomination
            ServerRunningA[(int)myKey] = (int)currentStatus;

        }
    }

    /// <summary>
    /// Gather the <see cref="ServerSlotData"/> of all <see cref="ServerSlot"/> in one place, so one can iterate over all of them.<br/>
    /// <remarks><list type="bullet">
    /// <item><seealso cref="LastIP"/>, <seealso cref="SelecIP"/> and alike offer more compact access to the domains.</item>
    /// <item>Accessing other data in this Dict is kind of ugly, but I cannot think of a much better way to implement it, as it shoud be iteratable and indexable.</item>
    /// </list> </remarks> 
    /// </summary>
    public static Dictionary<ServerSlot, ServerSlotData> ServerSlots = new()
    {
        { ServerSlot.last, new ServerSlotData("") },
        { ServerSlot.newIP, new ServerSlotData("") },
        { ServerSlot.slot1, new ServerSlotData("- if you can read this") },
        { ServerSlot.slot2, new ServerSlotData("- NetworkConfig") },
        { ServerSlot.slot3, new ServerSlotData("- GO TO -> 'Options'") },
        { ServerSlot.slot4, new ServerSlotData("-   -> 'Reset Options'") },
        { ServerSlot.slot5, new ServerSlotData("-   -> PRESS: 'Reset Configurations'") },
        { ServerSlot.selecIP, new ServerSlotData("") },
        { ServerSlot.localServer, new ServerSlotData(ServerAddressLocal) }
    };

    /// <summary>
    /// Iterate over all members of <see cref="ServerSlots"/> and have them update their <see cref="ServerSlotData.currentStatus"/><br/>
    /// <br/>
    /// Currently it is not possible to only load the domains via <see cref="StreamingAssetLoader.NetworkJSON_Load_x(string)"/>. If this changes, it might make sense to call the initial loading of the domains from in here. 
    /// </summary>
    public static IEnumerator UpdateAllServers()
    {
        foreach (ServerSlotData server in ServerSlots.Values)
        {
            yield return server.UpdateServerStatus();
        }
    }


    //Enum.GetNames(typeof(KnownServers)).Length; //Number of known Server slots, to be able to add more later

    /*
     * will be loaded from other config file,
     * and I am not going to refactor all of this as well, so just an indirection.
     * 
     * LastIP and SelecIP are likely useful shortcuts to the most relevant parts of 'ServerSlots' above, anyway.
     * 
     * (Also those are domain names (+ port) not IPs)
     */
    public static string LastIP { get => ServerSlots[ServerSlot.last].domain; set => ServerSlots[ServerSlot.last].domain = value; }
    public static string NewIP { get => ServerSlots[ServerSlot.newIP].domain; set => ServerSlots[ServerSlot.newIP].domain = value; }
    public static string IPslot1 { get => ServerSlots[ServerSlot.slot1].domain; set => ServerSlots[ServerSlot.slot1].domain = value; }
    public static string IPslot2 { get => ServerSlots[ServerSlot.slot2].domain; set => ServerSlots[ServerSlot.slot2].domain = value; }
    public static string IPslot3 { get => ServerSlots[ServerSlot.slot3].domain; set => ServerSlots[ServerSlot.slot3].domain = value; }
    public static string IPslot4 { get => ServerSlots[ServerSlot.slot4].domain; set => ServerSlots[ServerSlot.slot4].domain = value; }
    public static string IPslot5 { get => ServerSlots[ServerSlot.slot5].domain; set => ServerSlots[ServerSlot.slot5].domain = value; }
    public static string SelecIP { get => ServerSlots[ServerSlot.selecIP].domain; set => ServerSlots[ServerSlot.selecIP].domain = value; }

    /// <summary>
    /// IF YOU NEED THIS DATA, USE THE <see cref="ServerSlotData.currentStatus"/> OF THE <see cref="ServerSlots"/> MEMBERS.<br/>
    /// Holds the status of the stored Servers.<br/>
    /// This is acts like a function <see cref="ServerSlot"/> -> <see cref="ServerStatus"/> with a dont-care value at index 0.<br/>
    /// There are so many references to this abomination that I will leave it here and just update it, to be consistent with 
    /// <see cref="ServerSlots"/>.
    /// </summary>
    public static int[] ServerRunningA = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    //other, lastIP, newIP, IP1, IP2, IP3, selecIP, IP4, IP5,...} 
    //0: offline, 1: Checking, 2: online, 3: NoNetworkAddress;

    //------


    public static bool autoOSrecognition = true;

    public static OperationSystem Opsys = OperationSystem.Windows; //Scripts

    public enum OperationSystem
    {
        Windows = 0,
        Android = 1,
        iOS = 2,
        WindowsStoreApps = 3,
    }
    public static bool CursorVisDefault = true; //Script.
    public static bool CursorVisLastUsedIngame = true; //Script.

    public static bool GadgetCanBeUsed = false;


    // Configs
    public static bool VerboseURI = false;


    public enum Directories
    {
        misc,
        Stages,
        SaveGames,
        ValidationSets,
        FactStateMachines,
    }

    public static string CreateHierarchiePath(List<Directories> hierarchie, string prefix = "", string postfix = "")
    {
        foreach (var dir in hierarchie)
            prefix = Path.Combine(prefix, dir.ToString());

        return Path.Combine(prefix, postfix);
    }


    public static string Get_DataPath()
    {
        bool use_replacementfolder = false;

        if (Application.isEditor)
            return use_replacementfolder ? Path.Combine(Application.persistentDataPath, "DataPath_writeable") : Path.Combine(Application.dataPath, "StreamingAssets", "StreamToDataPath_withHandler");

        return Opsys switch
        {
            OperationSystem.Android => Path.Combine(Application.persistentDataPath, "DataPath_writeable"),
            OperationSystem.Windows or _ => Application.dataPath,
        };
    }

    // TODO! avoid tree traversel with name
    public static string CreatePathToFile(out bool file_exists, string name, string format = null, List<Directories> hierarchie = null, bool use_install_folder = false)
    {
        string ending = "";
        if (!string.IsNullOrEmpty(format))
            switch (format)
            {
                case "JSON":
                    ending = ".JSON";
                    break;
                default:
                    break;
            }

        string path = use_install_folder
            ? Get_DataPath()
            : Application.persistentDataPath;

        if (hierarchie != null)
        {
            path = CreateHierarchiePath(hierarchie, path);
            Directory.CreateDirectory(path);
        }

        path = Path.Combine(path, name + ending);
        file_exists = File.Exists(path);

        return path;
    }
}