using bessw.Unity.WebView.ChromeDevTools;
using bessw.Unity.WebView.ChromeDevTools.Protocol.Input;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace bessw.Unity.WebView
{
    [RequireComponent(typeof(RawImage))]
    public class WebViewComponent : MonoBehaviour, IPointerDownHandler, IPointerMoveHandler, IPointerUpHandler, IPointerEnterHandler, IDropHandler, IPointerExitHandler
    {
        #region json serializer

        /// <summary>
        /// Json serializer settings for the user space objects (e.g. transfering objects that have been droped on the BrowserView)
        /// Users are allowed to change serializer settings to their liking.
        /// </summary>
        public static JsonSerializerSettings serializerSettings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            Converters = new List<JsonConverter>()
                {
                    new ColorConverter(),
                    new Vector2Converter(),
                    new Vector3Converter(),
                    new Vector4Converter()
                }
        };
        /// <summary>
        /// JsonSerializer for the user space objects (e.g. transfering objects that have been droped on the BrowserView)
        /// Users are allowed to change serializer settings to their liking.
        /// </summary>
        public static JsonSerializer serializer = JsonSerializer.Create(serializerSettings);

        #endregion json serializer

        private Browser browser;
        protected BrowserTab tab;

        private RawImage rawImage;
        private RectTransform rectTransform;

        public bool headlessBrowser = true;
        //TODO: handle changed targetUrl
        public string targetUrl = "https://google.de";

        // Start is called before the first frame update
        private void Start()
        {
            Debug.LogWarning("start Webview");
            rawImage = this.gameObject.GetComponent<RawImage>();
            rectTransform = this.gameObject.GetComponent<RectTransform>();
        }

        private void OnEnable()
        {
            Debug.LogWarning("enable Webview");
            Browser.headless = headlessBrowser;
            browser = Browser.getInstance();

            //StartCoroutine(GetOpenTabs());
            var c = StartCoroutine(browser.OpenNewTab(targetUrl, (BrowserTab bt) =>
            {
                tab = bt;
                StartCoroutine(tab.Update());
                //StartCoroutine(createScreenshots());
                StartCoroutine(tab.StartStream(900, 560, (frame) =>
                {
                    Debug.LogWarning("stream");
                    rawImage.texture = frame;
                    rawImage.SetNativeSize();
                }));
            }));

        }

        public IEnumerator createScreenshots ()
        {
            yield return tab.CreateScreenshot(900, 560, (screenshot) =>
            {
                rawImage.texture = screenshot;
                StartCoroutine(createScreenshots());
            });
        }

        private IEnumerator getDropzoneState ()
        {
            Debug.LogWarning($"dropzone pre");

            DomNodeWrapper doc = null;
            yield return DomNodeWrapper.getDocument(tab, (document) => {
                Debug.LogWarning($"dropzone 1: '{document}'");
                doc = document;
                
                StartCoroutine(document.querySelectorAll("[dropzone='copy']", (dropzones) => {
                    foreach (var dropzone in dropzones)
                    {
                        Debug.LogWarning($"dropzone 2: Node is Null?: '{dropzone.Node == null}'");
                        StartCoroutine(dropzone.getAttributes((attributes) =>
                        {
                            Debug.LogWarning($"dropzone 3 getAttributes: '{string.Join(", ", attributes.Values)}'");
                        }));
                    }
                }));
            });
            Debug.LogWarning($"dropzone post: '{doc}'");

            // alternative way to get the dropzone state

        }

        // Update is called once per frame
        private void Update()
        {

        }

        #region pointer event handlers

        public void OnPointerDown(PointerEventData eventData)
        {
            Vector2Int pos = toBrowserCoordinates(eventData.position);
            tab.OnPointerDown(pos, eventData);
        }

        public void OnPointerMove(PointerEventData eventData)
        {
            // TODO: transform eventData delta vector to browser coordinates
            Vector2Int pos = toBrowserCoordinates(eventData.position);
            tab.OnPointerMove(pos, eventData);

            // On drag over
            if (eventData.dragging)
            {
                Debug.LogWarning($"OnDragOver: {eventData.position}");
                createDragEvent(DragEventType.dragOver, eventData);
            }
        }

        public void OnPointerUp(PointerEventData eventData)
        {
            Vector2Int pos = toBrowserCoordinates(eventData.position);
            tab.OnPointerUp(pos, eventData);
        }
        #endregion pointer event handlers

        #region drag event handlers
        public void OnPointerEnter(PointerEventData eventData)
        {
            if (eventData.dragging)
            {
                Debug.LogWarning($"OnDragEnter: {eventData.position}");
                createDragEvent(DragEventType.dragEnter, eventData);
            }
        }

        public void OnDrop(PointerEventData eventData)
        {
            Debug.LogWarning($"OnDrop: {eventData.position}");
            createDragEvent(DragEventType.drop, eventData);

            // TODO: remove debug code
            StartCoroutine(getDropzoneState());
        }

        public void OnPointerExit(PointerEventData eventData)
        {
            if (eventData.dragging)
            {
                Debug.LogWarning($"OnDragLeave: {eventData.position}");
                //createDragEvent(DragEventType.dragCancel, eventData);
                tab.CancelDragging();
                // TODO: drag cancel seems to be ignored by the browser
            }
        }

        private void createDragEvent(DragEventType dragEventType, PointerEventData eventData)
        {
            if (eventData.pointerDrag.TryGetComponent(out BrowserDropable dropable))
            {
                var position = toBrowserCoordinates(eventData.position);
                var dragEvent = new dispatchDragEvent
                {
                    type = dragEventType,
                    data = new DragData
                    {
                        items = new DragDataItem[]
                        {
                            new DragDataItem
                            {
                                mimeType = "application/json",
                                data = JsonConvert.SerializeObject(dropable, serializerSettings)
                            }
                        },
                        dragOperationsMask = DragOperationsMask.Copy
                    },
                    x = position.x,
                    y = position.y,
                };
                Debug.LogWarning($"DragEvent: {dragEvent.type}, {eventData.position}, '{dragEvent.data.items[0].data}'");
                // send the DragEvent as drag event to the browser
                tab.OnDragNDrop(dragEvent);
            }
        }
        #endregion drag event handlers

        private Vector2Int toBrowserCoordinates(Vector2 eventPos)
        {
            Vector2 localPoint;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventPos, null, out localPoint);

            // invert y because the browser has y=0 on the top
            Vector2 invertedLocalPos = new Vector2(localPoint.x, rectTransform.rect.size.y - localPoint.y);
            Vector2 browserCoorinate = tab.size / rectTransform.rect.size * invertedLocalPos;
            Debug.Log($"eventPos: {eventPos}, invertedLocalPos: {invertedLocalPos}, browserCoordinate: {browserCoorinate}");
            return new Vector2Int((int) browserCoorinate.x, (int) browserCoorinate.y);
        }

        private void OnDisable()
        {
            // TODO: do we want to close the browser when not in use?
            // close browser when recompiling
            tab.Close();
            browser.Close();
        }
        private void OnDestroy()
        {
            tab.Close();
            browser.Close();
        }
        /**
         * Close all browser windows.
         */
        private void OnApplicationQuit()
        {
            tab.Close();
            browser.Close();
        }
    }

    public interface BrowserDropable { }

}