using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
using System.Text.RegularExpressions;
#endif

// A UI element to show information about a debug entry
namespace IngameDebugConsole
{
	public class DebugLogItem : MonoBehaviour, IPointerClickHandler
	{
		#region Platform Specific Elements
#if !UNITY_2018_1_OR_NEWER
#if !UNITY_EDITOR && UNITY_ANDROID
		private static AndroidJavaClass m_ajc = null;
		private static AndroidJavaClass AJC
		{
			get
			{
				if( m_ajc == null )
					m_ajc = new AndroidJavaClass( "com.yasirkula.unity.DebugConsole" );

				return m_ajc;
			}
		}

		private static AndroidJavaObject m_context = null;
		private static AndroidJavaObject Context
		{
			get
			{
				if( m_context == null )
				{
					using( AndroidJavaObject unityClass = new AndroidJavaClass( "com.unity3d.player.UnityPlayer" ) )
					{
						m_context = unityClass.GetStatic<AndroidJavaObject>( "currentActivity" );
					}
				}

				return m_context;
			}
		}
#elif !UNITY_EDITOR && UNITY_IOS
		[System.Runtime.InteropServices.DllImport( "__Internal" )]
		private static extern void _DebugConsole_CopyText( string text );
#endif
#endif
		#endregion

#pragma warning disable 0649
		// Cached components
		[SerializeField]
		private RectTransform transformComponent;
		public RectTransform Transform { get { return transformComponent; } }

		[SerializeField]
		private Image imageComponent;
		public Image Image { get { return imageComponent; } }

		[SerializeField]
		private CanvasGroup canvasGroupComponent;
		public CanvasGroup CanvasGroup { get { return canvasGroupComponent; } }

		[SerializeField]
		private Text logText;
		[SerializeField]
		private Image logTypeImage;

		// Objects related to the collapsed count of the debug entry
		[SerializeField]
		private GameObject logCountParent;
		[SerializeField]
		private Text logCountText;

		[SerializeField]
		private RectTransform copyLogButton;
#pragma warning restore 0649

		// Debug entry to show with this log item
		private DebugLogEntry logEntry;
		public DebugLogEntry Entry { get { return logEntry; } }

		private DebugLogEntryTimestamp? logEntryTimestamp;
		public DebugLogEntryTimestamp? Timestamp { get { return logEntryTimestamp; } }

		// Index of the entry in the list of entries
		private int entryIndex;
		public int Index { get { return entryIndex; } }

		private bool isExpanded;
		public bool Expanded { get { return isExpanded; } }

		private Vector2 logTextOriginalPosition;
		private Vector2 logTextOriginalSize;
		private float copyLogButtonHeight;

		private DebugLogRecycledListView listView;

		public void Initialize( DebugLogRecycledListView listView )
		{
			this.listView = listView;

			logTextOriginalPosition = logText.rectTransform.anchoredPosition;
			logTextOriginalSize = logText.rectTransform.sizeDelta;
			copyLogButtonHeight = copyLogButton.anchoredPosition.y + copyLogButton.sizeDelta.y + 2f; // 2f: space between text and button

#if !UNITY_EDITOR && UNITY_WEBGL
			copyLogButton.gameObject.AddComponent<DebugLogItemCopyWebGL>().Initialize( this );
#endif
		}

		public void SetContent( DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp, int entryIndex, bool isExpanded )
		{
			this.logEntry = logEntry;
			this.logEntryTimestamp = logEntryTimestamp;
			this.entryIndex = entryIndex;
			this.isExpanded = isExpanded;

			Vector2 size = transformComponent.sizeDelta;
			if( isExpanded )
			{
				logText.horizontalOverflow = HorizontalWrapMode.Wrap;
				size.y = listView.SelectedItemHeight;

				if( !copyLogButton.gameObject.activeSelf )
				{
					copyLogButton.gameObject.SetActive( true );

					logText.rectTransform.anchoredPosition = new Vector2( logTextOriginalPosition.x, logTextOriginalPosition.y + copyLogButtonHeight * 0.5f );
					logText.rectTransform.sizeDelta = logTextOriginalSize - new Vector2( 0f, copyLogButtonHeight );
				}
			}
			else
			{
				logText.horizontalOverflow = HorizontalWrapMode.Overflow;
				size.y = listView.ItemHeight;

				if( copyLogButton.gameObject.activeSelf )
				{
					copyLogButton.gameObject.SetActive( false );

					logText.rectTransform.anchoredPosition = logTextOriginalPosition;
					logText.rectTransform.sizeDelta = logTextOriginalSize;
				}
			}

			transformComponent.sizeDelta = size;

			SetText( logEntry, logEntryTimestamp, isExpanded );
			logTypeImage.sprite = logEntry.logTypeSpriteRepresentation;
		}

		// Show the collapsed count of the debug entry
		public void ShowCount()
		{
			logCountText.text = logEntry.count.ToString();

			if( !logCountParent.activeSelf )
				logCountParent.SetActive( true );
		}

		// Hide the collapsed count of the debug entry
		public void HideCount()
		{
			if( logCountParent.activeSelf )
				logCountParent.SetActive( false );
		}

		// Update the debug entry's displayed timestamp
		public void UpdateTimestamp( DebugLogEntryTimestamp timestamp )
		{
			logEntryTimestamp = timestamp;

			if( isExpanded || listView.manager.alwaysDisplayTimestamps )
				SetText( logEntry, timestamp, isExpanded );
		}

		private void SetText( DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp, bool isExpanded )
		{
			if( !logEntryTimestamp.HasValue || ( !isExpanded && !listView.manager.alwaysDisplayTimestamps ) )
				logText.text = isExpanded ? logEntry.ToString() : logEntry.logString;
			else
			{
				StringBuilder sb = listView.manager.sharedStringBuilder;
				sb.Length = 0;

				if( isExpanded )
				{
					logEntryTimestamp.Value.AppendFullTimestamp( sb );
					sb.Append( ": " ).Append( logEntry.ToString() );
				}
				else
				{
					logEntryTimestamp.Value.AppendTime( sb );
					sb.Append( " " ).Append( logEntry.logString );
				}

				logText.text = sb.ToString();
			}
		}

		// This log item is clicked, show the debug entry's stack trace
		public void OnPointerClick( PointerEventData eventData )
		{
#if UNITY_EDITOR
			if( eventData.button == PointerEventData.InputButton.Right )
			{
				Match regex = Regex.Match( logEntry.stackTrace, @"\(at .*\.cs:[0-9]+\)$", RegexOptions.Multiline );
				if( regex.Success )
				{
					string line = logEntry.stackTrace.Substring( regex.Index + 4, regex.Length - 5 );
					int lineSeparator = line.IndexOf( ':' );
					MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>( line.Substring( 0, lineSeparator ) );
					if( script != null )
						AssetDatabase.OpenAsset( script, int.Parse( line.Substring( lineSeparator + 1 ) ) );
				}
			}
			else
				listView.OnLogItemClicked( this );
#else
			listView.OnLogItemClicked( this );
#endif
		}

		public void CopyLog()
		{
#if UNITY_EDITOR || !UNITY_WEBGL
			string log = GetCopyContent();
			if( string.IsNullOrEmpty( log ) )
				return;

#if UNITY_EDITOR || UNITY_2018_1_OR_NEWER || ( !UNITY_ANDROID && !UNITY_IOS )
			GUIUtility.systemCopyBuffer = log;
#elif UNITY_ANDROID
			AJC.CallStatic( "CopyText", Context, log );
#elif UNITY_IOS
			_DebugConsole_CopyText( log );
#endif
#endif
		}

		internal string GetCopyContent()
		{
			if( !logEntryTimestamp.HasValue )
				return logEntry.ToString();
			else
			{
				StringBuilder sb = listView.manager.sharedStringBuilder;
				sb.Length = 0;

				logEntryTimestamp.Value.AppendFullTimestamp( sb );
				sb.Append( ": " ).Append( logEntry.ToString() );

				return sb.ToString();
			}
		}

		public float CalculateExpandedHeight( DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp )
		{
			string text = logText.text;
			HorizontalWrapMode wrapMode = logText.horizontalOverflow;

			SetText( logEntry, logEntryTimestamp, true );
			logText.horizontalOverflow = HorizontalWrapMode.Wrap;

			float result = logText.preferredHeight + copyLogButtonHeight;

			logText.text = text;
			logText.horizontalOverflow = wrapMode;

			return Mathf.Max( listView.ItemHeight, result );
		}

		// Return a string containing complete information about the debug entry
		public override string ToString()
		{
			return logEntry.ToString();
		}
	}
}