﻿using System;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using BestHTTP.SocketIO;

namespace Tap.Tilt
{
    // Handles all socketio network transport
    [RequireComponent(typeof(GameController))]
    [RequireComponent(typeof(GameData))]
    public class SocketIONetwork : MonoBehaviour
    {
        // static instance of self
        public static SocketIONetwork instance;

        // A text mesh which shows the root of the selected url to the users
        public TextMesh showUrl;

        // The game controller where the data is sent to manage the game
        private GameController gc;

        // The game data to use for this instance
        private GameData gameData;

        private Socket Socket;
        private Socket RootSocket;

        // number of seconds between server pings
        private readonly float pingDelay = 5.0f;

        // The last ping time
        private float lastPing = 0f;

        private SocketManager manager;

        // Set to true when connected
        private bool connectState = false;

        // Reconnect interval for reconnect attempts
        private readonly float reconnectInterval = 1f;
        private float lastAttempt;

        // Create events for the server to respond to
        void Awake()
        {
            instance = instance ?? this;
            gc = gc ?? GetComponent<GameController>();
            gameData = gameData ?? GetComponent<GameData>();
            SocketSetup();
        }

        public void SocketConnect()
        {
            connectState = true;
            // Build an options object and disable reconnect to keep the system from timing itself out
            SocketOptions options = new SocketOptions
            {
                Reconnection = false
            };

            // Create the SocketManager instance
            manager = new SocketManager(new Uri(gameData.socketUrl), options);

            // Start the socket
            Debug.Log("Starting Socket on server " + gameData.socketUrl);
            Socket = manager["/display"];
            RootSocket = manager.Socket;
        }

        public void SocketEvents()
        {
            // Add error handler, so we can display it
            Socket.On(SocketIOEventTypes.Error, OnError);

            // Set up our event handlers.
            Socket.On(SocketIOEventTypes.Connect, OnConnected);

            Socket.On(SocketIOEventTypes.Disconnect, OnDisconnected);

            // Generic control changes
            Socket.On("control", (socket, packet, args) =>
            {
                // Debug.Log("control event on socket" + packet.ToString());

                ControlData cd = JsonUtility.FromJson<ControlData>(MessageData(packet.ToString()));

                // Send the control data to the Game Controller
                gc.UserControl(cd);
            });

            // User requests an orientation reset
            Socket.On("reset", (socket, packet, args) =>
            {
                ControlData cd = JsonUtility.FromJson<ControlData>(MessageData(packet.ToString()));
                gc.UserReset(cd.i);
            });

            // User requests location
            Socket.On("locate", (socket, packet, args) =>
            {
                ControlData cd = JsonUtility.FromJson<ControlData>(MessageData(packet.ToString()));
                gc.UserPing(cd.i);
            });


            Socket.On("controlsDisconnect", (socket, packet, args) =>
            {
                Debug.Log("disconnect event on socket" + packet.ToString());
                gc.UserExit(MessageData(packet.ToString().Replace("\"", "")));
            });

        }

        public void SocketSetup()
        {

            // Show the user URL
            if (showUrl != null)
                showUrl.text = gameData.shortLink == "" ? gameData.socketUrl.Replace("/socket.io/display", "") : gameData.shortLink;
        }

        /// <summary>
        /// Socket connected event.
        /// </summary>
        private void OnConnected(Socket socket, Packet packet, params object[] args)
        {
            Debug.Log("OnConnected");
            connectState = true;
            Identify();
        }
        private void OnDisconnected(Socket socket, Packet packet, params object[] args)
        {
            Debug.Log("DISCONNECTED");
            connectState = false;
        }

        private void LateUpdate()
        {
            // If not connected the try to reconnect at the interval rate
            if (!connectState && lastAttempt + reconnectInterval < Time.time)
            {
                lastAttempt = Time.time;
                SocketConnect();
                SocketEvents();
            }
        }
        /// <summary>
        /// Identify this instance.
        /// </summary>
        private void Identify()
        {
            // Check if there's a saved identity for this instance
            // Attach the unity id to the identity
            string identity = PlayerPrefs.GetString("deviceUniqueIdentifier", SystemInfo.deviceUniqueIdentifier);

            // Save the id
            PlayerPrefs.SetString("deviceUniqueIdentifier", identity);

            // Send the identity of this server
            Socket.Emit("identify", JsonUtility.ToJson(identity));
        }

        /// <summary>
        /// Called on local or remote error.
        /// </summary>
        private void OnError(Socket socket, Packet packet, params object[] args)
        {
            Error error = args[0] as Error;
            switch (error.Code)
            {
                case SocketIOErrors.User:
                    Debug.Log("Exception in an event handler!");
                    break;
                case SocketIOErrors.Internal:
                    Debug.Log("Internal error!");
                    break;
                default:
                    Debug.Log("Server error! " + error.Code.ToString() + " " + error.Message.ToString());
                    break;
            }
            Debug.Log(error.ToString());
        }

        public void Send(string eventName, params object[] data)
        {
            // Debug.Log("Socket Send " + eventName + data.ToString());
            Socket.Emit(eventName, data);
        }

        // A sender which sends an event to one multiplayer target
        public void SendEvent(string connectionId, string eventName, string eventValue)
        {
            // Debug.Log("SendEvent " + connectionId + " eventName: " + eventName + " value: " + eventValue.ToString());

            Socket.Emit("event", connectionId, eventName, eventValue);
        }

        // Helper to extract data from JSON encoded strings
        private string MessageData(string message)
        {
            int startIndex = message.IndexOf(",");
            return (startIndex >= 1) ? message.Substring(startIndex + 1, message.Length - (startIndex + 2)) : "";
        }

        // Update is called once per frame
        void Update()
        {
            if (RootSocket != null && lastPing + pingDelay > Time.time)
            {
                lastPing = Time.time;
                RootSocket.Emit("ping");
            }
        }
    }
}