using System;
using System.Collections;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using Beefy;
using Beefy.widgets;
using Beefy.theme;
using Beefy.gfx;
using Beefy.theme.dark;
using Beefy.events;
using Beefy.utils;
using IDE.Debugger;


namespace IDE.ui
{	
	class ProfilePanel : Panel
	{
		class ProfileListViewItem : DarkListViewItem
		{
			public int32 mSelfSamples;
			public int32 mChildSamples;

			float ArrowCenterY
			{
				get
				{
					return DarkTheme.sUnitSize * 0.5f + GS!(4) + 1;
				}
			}

			protected override void DrawChildren(Graphics g, int itemStart, int itemEnd)
			{
				base.DrawChildren(g, itemStart, itemEnd);

				if (mParentItem == null)
					return;

				float barWidth = (int)GS!(1.5f);

				let lastItem = mChildItems.Back;
				if (!lastItem.mVisible)
					return;

				let parent = (DarkListView)mListView;
				float absX = (mDepth - 1) * parent.mChildIndent + GS!(12);
				if (absX >= parent.mColumns[0].mWidth)
					return;

				using (g.PushColor(0xFFB0B0B0))
					g.FillRect((int)ArrowCenterY, GS!(14), barWidth, (int)lastItem.mY - (int)GS!(4.8f));
			}

			public override void DrawAll(Graphics g)
			{
				float barWidth = (int)GS!(1.5f);

				var listView = (DarkListView)mListView;
				float xStart = mDepth * listView.mChildIndent - GS!(12);
				if (xStart < listView.mColumns[0].mWidth)
				{
					if ((mVisible) && (mColumnIdx == 0) && (mX > 10))
					{
						using (g.PushColor(0xFFB0B0B0))
						{
							float horzWidth = GS!(13) - 3 - barWidth;
							if (mOpenButton == null)
								horzWidth += GS!(4);
		                    g.FillRect(-mX + (int)(ArrowCenterY + GS!(0.7f)), GS!(9), horzWidth, barWidth);
						}
					}
				}

				base.DrawAll(g);
			}
		}

		class ProfileListView : DarkListView
		{
			public this()
			{
				//mShowLineGrid = true;
			}

			protected override ListViewItem CreateListViewItem()
			{
				return new ProfileListViewItem();
			}

			protected override void SetScaleData()
			{
				base.SetScaleData();

				mIconX = GS!(4);
				mOpenButtonX = GS!(4);
				mLabelX = GS!(23);
				mChildIndent = GS!(16);
				mHiliteOffset = GS!(-2);
			}
		}

		public class SessionComboBox : DarkComboBox
		{
			public bool mIsRecording;

			public override void Draw(Graphics g)
			{
				base.Draw(g);
			}

			public override void Update()
			{
				base.Update();
			}

			public override void DrawAll(Graphics g)
			{
				base.DrawAll(g);

				mLabelX = mIsRecording ? GS!(24) : GS!(8);
				if (mIsRecording)
					g.Draw(DarkTheme.sDarkTheme.GetImage(.RedDot), GS!(4), GS!(0));
			}
		}

        ProfileListView mListView;
		public SessionComboBox mSessionComboBox;
		public DarkComboBox mThreadComboBox;
		DarkButton mStopButton;
        public bool mThreadsDirty = true;
        public int32 mThreadIdx;
		public int32 mDisabledTicks;
		public DbgProfiler mProfiler;
		public List<int32> mThreadList = new List<int32>() ~ delete _;
		public uint32 mTickCreated;
		public int32 mCurIdx;
		public DbgProfiler mUserProfiler;
		public DbgProfiler.Overview mShowOverview ~ delete _;
		public int mOverviewAge;
		public bool mAwaitingLoad;
		public bool mIsSamplingHidden;

		public List<DbgProfiler> mProfilers = new List<DbgProfiler>() ~ DeleteContainerAndItems!(_);

		const String cStartProfilingCmd = " < Start Profiling > ";
		const String cStartProfilingExCmd = " < Profile... > ";

		public bool IsSamplingHidden
		{
			get
			{
				return mIsSamplingHidden;
			}
		}

        public this()
        {
			mSessionComboBox = new SessionComboBox();
			mSessionComboBox.mPopulateMenuAction.Add(new => PopulateSessionList);
			AddWidget(mSessionComboBox);
			mSessionComboBox.Label = cStartProfilingCmd;
			mTabWidgets.Add(mSessionComboBox);

			mStopButton = new DarkButton();
			mStopButton.Label = "Stop";
			AddWidget(mStopButton);
			mStopButton.mOnMouseClick.Add(new (evt) =>
				{
					if (mProfiler != null)
						mProfiler.Stop();
				});

			mThreadComboBox = new DarkComboBox();            
			mThreadComboBox.mPopulateMenuAction.Add(new => PopulateThreadList);
			AddWidget(mThreadComboBox);
			mTabWidgets.Add(mThreadComboBox);

            mListView = new ProfileListView();
            mListView.InitScrollbars(true, true);
            mListView.mHorzScrollbar.mPageSize = GS!(100);
            mListView.mHorzScrollbar.mContentSize = GS!(500);
            mListView.mVertScrollbar.mPageSize = GS!(100);
            mListView.mVertScrollbar.mContentSize = GS!(500);
            mListView.UpdateScrollbars();
			mListView.mOnMouseDown.Add(new (mouseArgs) =>
				{
					SetFocus();
				});
			mListView.mOnItemMouseDown.Add(new => ValueMouseDown);
			mTabWidgets.Add(mListView);

            //mListView.mDragEndHandler += HandleDragEnd;
            //mListView.mDragUpdateHandler += HandleDragUpdate;

            AddWidget(mListView);

            //mListView.mMouseDownHandler += ListViewMouseDown;

            ListViewColumn column = mListView.AddColumn(300, "Function");
            column.mMinWidth = 100;

            column = mListView.AddColumn(80, "Total CPU %");
            column.mMinWidth = 60;
            column = mListView.AddColumn(80, "Self CPU %");
            column.mMinWidth = 60;
			column = mListView.AddColumn(90, "Total CPU (ms)");
			column.mMinWidth = 60;
			column = mListView.AddColumn(90, "Self CPU (ms)");
			column.mMinWidth = 60;
            //RebuildUI();
        }

		public void Clear()
		{
			mUserProfiler = null;
			mProfiler = null;
			ClearAndDeleteItems(mProfilers);

			mSessionComboBox.Label = cStartProfilingCmd;
			mThreadComboBox.Label = "";
			mListView.GetRoot().Clear();
			mThreadList.Clear();
			DeleteAndNullify!(mShowOverview);
			mAwaitingLoad = false;
		}

		public void StartProfiling(int threadId, String desc, int sampleRate)
		{
			if (mUserProfiler != null)
				return;

			if (!gApp.mDebugger.mIsRunning)
			{
				gApp.RunWithCompiling();

				gApp.QueueProfiling(threadId, desc, sampleRate);
				return;
			}

			mUserProfiler = gApp.mDebugger.StartProfiling(threadId, desc, sampleRate);
			mUserProfiler.mIsManual = true;
			Add(mUserProfiler);
			Show(mUserProfiler);
		}

		public void StartProfiling()
		{
			StartProfiling(0, "", gApp.mSettings.mDebuggerSettings.mProfileSampleRate);
		}

		public void StopProfiling()
		{
			if (mUserProfiler == null)
				return;

			mUserProfiler.Stop();
			mUserProfiler = null;
		}

		void PopulateSessionList(Menu menu)
		{
			var item = menu.AddItem(cStartProfilingCmd);
			item.mOnMenuItemSelected.Add(new (item) =>
				{
					StartProfiling();
				});

			item = menu.AddItem(cStartProfilingExCmd);
			item.mOnMenuItemSelected.Add(new (item) =>
				{
					var profileDialog = new ProfileDialog();
					profileDialog.PopupWindow(mWidgetWindow);
					//StartProfilingEx();
				});

			for (int profilerIdx = mProfilers.Count - 1; profilerIdx >= 0; profilerIdx--)
			{
				var profiler = mProfilers[profilerIdx];
				var label = scope String();
				profiler.GetLabel(label);
				item = menu.AddItem(label);

				var overview = scope DbgProfiler.Overview();
				profiler.GetOverview(overview);
				if (overview.IsSampling)
					item.mIconImage = DarkTheme.sDarkTheme.GetImage(.RedDot);
				item.mOnMenuItemSelected.Add(new (item) =>
					{
						mSessionComboBox.Label = item.mLabel;
						Show(profiler);
					});
			}
		}

		void PopulateThreadList(Menu menu)
		{
			if (mProfiler == null)
				return;

			if (mProfiler.IsSampling)
				return;

			var subItem = menu.AddItem("All Threads");
			subItem.mOnMenuItemSelected.Add(new (item) => { Show(0, .()); });

			var threadListStr = scope String();
			mProfiler.GetThreadList(threadListStr);

			for (var entry in threadListStr.Split('\n'))
			{
				if (entry.Length == 0)
					continue;

				var dataItr = entry.Split('\t');

				int32 threadId = int32.Parse(dataItr.GetNext());
				StringView threadName = dataItr.GetNext();

				String threadStr = null;
				var str = scope String();
				str.AppendF("{0}", threadId);
				if (!threadName.IsEmpty)
				{
					threadStr = new String(threadName);
					str.AppendF(" - {0}", threadName);
				}

				subItem = menu.AddItem(str);
				subItem.mOnMenuItemSelected.Add(new (item) => { Show(threadId, threadStr); } ~ delete threadStr);
			}
		}

		public void Show(int32 threadId, StringView threadName)
		{
			mTickCreated = Utils.GetTickCount();

			if (threadId == 0)
				mThreadComboBox.Label = "All Threads";
			else
			{
				var threadStr = scope String();
				threadStr.AppendF("{0}", threadId);
				if (!threadName.IsEmpty)
					threadStr.AppendF(" - {0}", threadName);
				mThreadComboBox.Label = threadStr;
			}

			mThreadList.Clear();

			var str = scope String();
			mProfiler.GetCallTree(threadId, str, false);
			
			List<DarkListViewItem> itemStack = scope List<DarkListViewItem>();
			DarkListViewItem curItem = (DarkListViewItem)mListView.GetRoot();

			mListView.GetRoot().Clear();

			var overview = scope DbgProfiler.Overview();
			mProfiler.GetOverview(overview);

			int32 totalSamples = 0;
			float sampleTimeMult = 1000.0f / overview.mSamplesPerSecond; // 1ms per sample

			for (var lineView in str.Split('\n'))
			{
				var line = scope String(lineView);
				if (line.Length == 0)
					continue;
				if (line == "-")
				{
					curItem = itemStack.PopBack();

					curItem.mChildItems.Sort(scope (baseA, baseB) => 
			        	{
							var a = (ProfileListViewItem)baseA;
							var b = (ProfileListViewItem)baseB;
							return (b.mChildSamples + b.mSelfSamples) - (a.mChildSamples + a.mSelfSamples);
			            });

					continue;
				}
				
				var dataList = scope List<StringView>(line.Split('\t'));
				
				ProfileListViewItem newItem = (ProfileListViewItem)curItem.CreateChildItem();
				newItem.mLabel = new String(dataList[0]);				
				//newItem.mOnMouseDown.Add(new => ValueClicked);

				var selfStr = scope String(dataList[1]);
				var childStr = scope String(dataList[2]);

				newItem.mSelfSamples = int32.Parse(selfStr);
				newItem.mChildSamples = int32.Parse(childStr);

				if (totalSamples == 0)
					totalSamples = newItem.mChildSamples;

				var subItem = newItem.CreateSubItem(1);
				var tempStr = scope String();
				if (totalSamples == 0)
					tempStr.Append("0.00%");
				else
					tempStr.AppendF("{0:0.00}%", (newItem.mSelfSamples + newItem.mChildSamples) * 100.0f / totalSamples);
				subItem.Label = tempStr;
				//subItem.mOnMouseDown.Add(new => ValueClicked);

				subItem = newItem.CreateSubItem(2);
				tempStr.Clear();
				if (totalSamples == 0)
					tempStr.Append("0.00%");
				else
					tempStr.AppendF("{0:0.00}%", newItem.mSelfSamples * 100.0f / totalSamples);
				subItem.Label = tempStr;
				//subItem.mOnMouseDown.Add(new => ValueClicked);

				subItem = newItem.CreateSubItem(3);
				tempStr.Clear();
				tempStr.AppendF("{0}ms", (int32)((newItem.mSelfSamples + newItem.mChildSamples) * sampleTimeMult + 0.5f));
				subItem.Label = tempStr;
				//subItem.mOnMouseDown.Add(new => ValueClicked);

				subItem = newItem.CreateSubItem(4);
				tempStr.Clear();
				tempStr.AppendF("{0}ms", (int32)(newItem.mSelfSamples * sampleTimeMult + 0.5f));
				subItem.Label = tempStr;
				//subItem.mOnMouseDown.Add(new => ValueClicked);

				itemStack.Add(curItem);
				curItem = newItem;
			}
		}

		public void Add(DbgProfiler profiler)
		{
			profiler.mIdx = mCurIdx++;
			mProfilers.Add(profiler);
		}

		public void UpdateOverview(out bool changed)
		{
			changed = false;
			int prevActualSamples = (mShowOverview?.mTotalActualSamples).GetValueOrDefault();

			DeleteAndNullify!(mShowOverview);

			if (mProfiler == null)
				return;

			mShowOverview = new DbgProfiler.Overview();
			mProfiler.GetOverview(mShowOverview);
			changed = mShowOverview.mTotalActualSamples != prevActualSamples;
			mOverviewAge = 0;
		}

		public void Show(DbgProfiler profiler)
		{
			mThreadList.Clear();

			mProfiler = profiler;

			UpdateOverview(var changed);

			var overview = scope DbgProfiler.Overview();
			profiler.GetOverview(overview);

			var label = scope String();
			profiler.GetLabel(label);
			mSessionComboBox.Label = label;

			if (overview.IsSampling)
			{
				mAwaitingLoad = true;
				mThreadComboBox.Label = "";
				mListView.GetRoot().Clear();
				ResizeComponents();
				return;
			}

			mAwaitingLoad = false;
			var threadListStr = scope String();
			mProfiler.GetThreadList(threadListStr);

			for (var entry in threadListStr.Split('\n'))
			{
				if (entry.Length == 0)
					continue;
				var dataItr = entry.Split('\t');
				int32 threadId = int32.Parse(dataItr.GetNext());
				
				mThreadList.Add(threadId);
			}

			if (mThreadList.Count == 0)
				return;

			int32 threadId = mThreadList[0];
			Show(threadId, .());
			ResizeComponents();
		}

        public override void Serialize(StructuredData data)
        {
            base.Serialize(data);

            data.Add("Type", "ProfilePanel");
        }

        public override bool Deserialize(StructuredData data)
        {
            return base.Deserialize(data);
        }

        void ValueMouseDown(ListViewItem item, float x, float y, int32 btnNum, int32 btnCount)
        {
#unwarn
            DarkListViewItem baseItem = (DarkListViewItem)item.GetSubItem(0);

			ListViewItemMouseDown(item, x, y, btnNum, btnCount);

            if ((btnNum == 0) && (btnCount > 1))
            {
                /*for (int childIdx = 0; childIdx < mListView.GetRoot().GetChildCount(); childIdx++)
                {
                    var checkListViewItem = mListView.GetRoot().GetChildAtIndex(childIdx);
                    checkListViewItem.IconImage = null;
                }

                int selectedIdx = mListView.GetRoot().GetIndexOfChild(item);

                int threadId = int.Parse(item.mLabel);
                IDEApp.sApp.mDebugger.SetActiveThread(threadId);                
                IDEApp.sApp.mDebugger.mCallStackDirty = true;
                IDEApp.sApp.mCallStackPanel.MarkCallStackDirty();
                IDEApp.sApp.mCallStackPanel.Update();
                IDEApp.sApp.mWatchPanel.MarkWatchesDirty(true);
                IDEApp.sApp.mAutoWatchPanel.MarkWatchesDirty(true);
                IDEApp.sApp.mMemoryPanel.MarkViewDirty();
                IDEApp.sApp.ShowPCLocation(IDEApp.sApp.mDebugger.mSelectedCallStackIdx, false, true);
                
                item.IconImage = DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.ArrowRight);*/
            }
        }

		void ResizeComponents()
		{
			mThreadComboBox.Resize(GS!(80), GS!(22), GS!(220), GS!(24));
			mListView.Resize(0, GS!(44), mWidth, Math.Max(mHeight - GS!(44), 0));

			if (mSessionComboBox.mIsRecording)
			{
				mStopButton.mVisible = true;
				mStopButton.Resize(GS!(28), GS!(1), GS!(52), GS!(22));
				//mSessionComboBox.Resize(GS!(80) + GS!(42), GS!(1), GS!(220) - GS!(42), GS!(24));
				mSessionComboBox.Resize(GS!(80), GS!(1), GS!(220), GS!(24));
			}
			else
			{
				mStopButton.mVisible = false;
				mSessionComboBox.Resize(GS!(80), GS!(1), GS!(220), GS!(24));
			}
		}

        public override void Resize(float x, float y, float width, float height)
        {
            base.Resize(x, y, width, height);

			ResizeComponents();
        }

        public void MarkThreadsDirty()
        {
            mThreadsDirty = true;
        }

        public override void LostFocus()
        {
            base.LostFocus();
            mListView.GetRoot().SelectItemExclusively(null);
        }

        public override void Update()
        {
            base.Update();

			if (mUserProfiler != null)
			{
				if (!mUserProfiler.IsSampling)
					mUserProfiler = null;
			}

			if (mProfiler == null)
			{
				if (!mProfilers.IsEmpty)
				{
					var profiler = mProfilers.Back;
					Show(profiler);
					MarkDirty();
				}
			}

            if (mProfiler != null)
			{
				++mOverviewAge;
				if (mOverviewAge >= 15)
				{
					UpdateOverview(var changed);
					if (changed)
						MarkDirty();
				}

				if (mUpdateCnt % 60 == 0)
					MarkDirty();

				if ((!mProfiler.IsSampling) && (mAwaitingLoad))
					Show(mProfiler);
			}

			if (mShowOverview != null)
				mSessionComboBox.mIsRecording = mShowOverview.IsSampling;
			else
				mSessionComboBox.mIsRecording = false;
			ResizeComponents();
        }

		public void ForcedUpdate()
		{
			bool isSamplingHidden = false;
			if ((mWidgetWindow == null) || (mProfiler == null) || (!mProfiler.IsSampling))
			{
				for (var profile in mProfilers)
					if (profile.IsSampling)
						isSamplingHidden = true;
			}

			if (mIsSamplingHidden != isSamplingHidden)
			{
				mIsSamplingHidden = isSamplingHidden;
				gApp.MarkDirty();
			}
		}

        public override void DrawAll(Graphics g)
        {
            base.DrawAll(g);
        }

		public override void Draw(Graphics g)
		{
			base.Draw(g);

			g.SetFont(DarkTheme.sDarkTheme.mSmallFont);
			g.DrawString("Session", mSessionComboBox.mX - GS!(2), mSessionComboBox.mY, .Right);
			g.DrawString("Thread", mThreadComboBox.mX - GS!(2), mThreadComboBox.mY, .Right);

			if ((mProfiler != null) && (mShowOverview != null))
			{
				g.DrawString(StackStringFormat!("Rate: {0} Hz", mShowOverview.mSamplesPerSecond), GS!(320), GS!(2));
				int32 seconds = (mShowOverview.mRecordedTicks / 1000);
				g.DrawString(StackStringFormat!("Length: {0}:{1:02}.{2}", seconds / 60, seconds % 60, (mShowOverview.mRecordedTicks % 1000)/100), GS!(320), GS!(22));

				seconds = (mShowOverview.mEndedTicks / 1000);
				if (seconds > 60*60)
					g.DrawString(StackStringFormat!("Age: {0}:{1:02}:{2:02}", seconds / 60 / 60, (seconds / 60) % 60, seconds % 60), GS!(420), GS!(22));
				else
					g.DrawString(StackStringFormat!("Age: {0}:{1:02}", seconds / 60, seconds % 60), GS!(420), GS!(22));

				g.DrawString(StackStringFormat!("Samples: {0}", mShowOverview.mTotalActualSamples), GS!(420), GS!(2));
				g.DrawString(StackStringFormat!("Missed Samples: {0}", mShowOverview.mTotalVirtualSamples - mShowOverview.mTotalActualSamples), GS!(550), GS!(2));
			}

			if (mTickCreated != 0)
			{
				
				/*String text = scope String();
				text.AppendF("{0}s ago", (int32)(Utils.GetTickCount() - mTickCreated) / 1000);
				g.DrawString(text, mWidth - 4, 2, FontAlign.Right);*/
			}
		}

		public override void FocusForKeyboard()
		{
			base.FocusForKeyboard();
			mSessionComboBox.SetFocus();
		}

		public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
		{
			base.MouseDown(x, y, btn, btnCount);
			SetFocus();
		}
    }
}
