using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.Scraper;
using skyscraper5.Skyscraper.Scraper.Storage;
using skyscraper5.UI.Overrides;
using skyscraper5.UI.StreamAcquisition;
using skyscraper8.Skyscraper.Scraper.Storage;

namespace skyscraper5.UI
{
	public partial class Form1 : Form, INodeEngine
	{
		public Form1()
		{
			InitializeComponent();
		}

		public Form1(StreamSource acquiredStream)
			: this()
		{
			AcquiredStream = acquiredStream;
			tsContext = new TsContext();
			tsContext.OnPacketLoss += TsContext_OnPacketLoss;
			skyscraperContext = new SkyscraperContext(tsContext, dataStorage, objectStorage);
			skyscraperContext.UiJunction = uiJunction;
		}

		private void TsContext_OnPacketLoss(uint pid, uint oldContinuity, uint newContinuity, long packetLossEvents)
		{
			continuityErrors = packetLossEvents;
		}

		public StreamSource AcquiredStream { get; }
		private TsContext tsContext;
		private SkyscraperContext skyscraperContext;

		private DataStorage dataStorage;
		private ObjectStorage objectStorage;

		private Form1UiJunction uiJunction;
		private DateTime windowOpenedTimestamp;
		private TimeSpan windowOpenDuration;
		private long continuityErrors;
		private TeiCounter teiCounterFilter;
		private PmtCounter pmtCounter;
		private PacketsPerSecondCounter packetsPerSecondCounter;
		private ScrambleCounter scrambleCounter;


		private void Form1_Load(object sender, EventArgs e)
		{
			scrambleCounter = new ScrambleCounter();
			packetsPerSecondCounter = new PacketsPerSecondCounter();
			teiCounterFilter = new TeiCounter();
			skyscraperContext.InitalizeFilterChain(teiCounterFilter, packetsPerSecondCounter, scrambleCounter);

			uiJunction = new Form1UiJunction(this);
			skyscraperContext.UiJunction = uiJunction;


			windowOpenedTimestamp = DateTime.Now;
			windowOpenDuration = new TimeSpan(0, 0, 0);
			bottomLeft.Items[0].SubItems[1].Text = AcquiredStream.GetSourceName();

			skyscraperContext.PatDecoder.OnValidSection += PsiDecoder_OnValidSection;
			skyscraperContext.PatDecoder.OnCrcError += PsiDecoder_OnCrcError;
			pmtCounter = new PmtCounter();
			skyscraperContext.PmtDecoderTransformer = pmtCounter;
			skyscraperContext.CatDecoder.OnValidSection += PsiDecoder_OnValidSection;
			skyscraperContext.CatDecoder.OnCrcError += PsiDecoder_OnCrcError;
			skyscraperContext.NitDecoder.OnValidSection += PsiDecoder_OnValidSection;
			skyscraperContext.NitDecoder.OnCrcError += PsiDecoder_OnCrcError;
			skyscraperContext.Pid0x11Decoder.OnValidSection += PsiDecoder_OnValidSection;
			skyscraperContext.Pid0x11Decoder.OnCrcError += PsiDecoder_OnCrcError;
			skyscraperContext.Pid0x12Decoder.OnValidSection += PsiDecoder_OnValidSection;
			skyscraperContext.Pid0x12Decoder.OnCrcError += PsiDecoder_OnCrcError;
			DrawBottomCenter();

			pidListView_ColumnClick(this, new ColumnClickEventArgs(0));

			AcquiredStream.StartSource(skyscraperContext);
		}

		private long patErrors, catErrors, nitErrors, sdtErrors, batErrors, eitErrors;
		private void PsiDecoder_OnCrcError(PsiSection section, int pid, long available)
		{
			switch (section.TableId)
			{
				case 0: patErrors++; break;
				case 1: catErrors++; break;
				case 0x40: nitErrors++; break;
				case 0x41: nitErrors++; break;
				case 0x42: sdtErrors++; break;
				case 0x46: sdtErrors++; break;
				case 0x4a: batErrors++; break;
				default:
					if (section.TableId >= 0x4e && section.TableId <= 0x6f)
					{
						eitErrors++;
						break;
					}
					throw new NotImplementedException();
			}
		}

		private long patValids, catValids, nitValids, sdtValids, batValids, eitValids;
		private void PsiDecoder_OnValidSection(PsiSection section, int pid, long available)
		{
			switch (section.TableId)
			{
				case 0: patValids++; break;
				case 1: catValids++; break;
				case 0x40: nitValids++; break;
				case 0x41: nitValids++; break;
				case 0x42: sdtValids++; break;
				case 0x46: sdtValids++; break;
				case 0x4a: batValids++; break;
				default:
					if (section.TableId >= 0x4e && section.TableId <= 0x6f)
					{
						eitValids++;
						break;
					}
					throw new NotImplementedException();
			}
		}

		private void Form1_FormClosed(object sender, FormClosedEventArgs e)
		{
			AcquiredStream.StopSource();
		}

		private void timer1_Tick(object sender, EventArgs e)
		{
			windowOpenDuration = windowOpenDuration.Add(new TimeSpan(0, 0, 1));
			bottomLeft.Items[1].SubItems[1].Text = windowOpenDuration.ToString();
			DrawBottomCenter();
			RenderPidList();
			bottomLeft.Items[2].SubItems[1].Text = AcquiredStream.State.ToString();
		}

		private string bottomCenterTemplate;
		private void DrawBottomCenter()
		{
			if (string.IsNullOrWhiteSpace(bottomCenterTemplate))
			{
				bottomCenterTemplate = bottomCenter.Text;
			}

			long patSections = patValids;
			long pmtSections = pmtCounter.ValidSection; ;
			long catSections = catValids;
			long nitSections = nitValids;
			long sdtSections = sdtValids;
			long eitSections = eitValids;
			long patCrcErrors = patErrors;
			long pmtCrcErrors = pmtCounter.CrcErrors;
			long catCrcErrors = catErrors;
			long nitCrcErrors = nitErrors;
			long sdtCrcErrors = sdtErrors;
			long eitCrcErrors = eitErrors;
			long continuityErrors = this.continuityErrors;
			string muxBitrate = "???";
			long teiErrors = this.teiCounterFilter.TransportErrors;
			string lastSec = RenderPacketsPerSecond();
			long syncLosses = 0;
			long inBuffer = 0;
			long outBuffer = 0;

			bottomCenter.Text = String.Format(bottomCenterTemplate, patSections, pmtSections, catSections, nitSections, sdtSections, eitSections, patCrcErrors,
				pmtCrcErrors, catCrcErrors, nitCrcErrors, sdtCrcErrors, eitCrcErrors, continuityErrors, muxBitrate, teiErrors, lastSec, syncLosses, inBuffer, outBuffer);
		}

		private char[] dataUnits = { 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q' };
		private string RenderPacketsPerSecond()
		{
			if (!packetsPerSecondCounter.Ready)
			{
				return "???";
			}

			long packetsPerSecond = packetsPerSecondCounter.PacketsPerSecond;
			long bytesPerSecond = packetsPerSecond * 188;
			long bitsPerSecond = bytesPerSecond * 8;
			if (bitsPerSecond < 1000)
			{
				return String.Format("{0} bits/s", bytesPerSecond);
			}

			int unitOffset = -1;
			while (bitsPerSecond > 1000)
			{
				bitsPerSecond /= 1000;
				unitOffset++;
			}
			return String.Format("{0} {1}bits/s", bitsPerSecond, dataUnits[unitOffset]);
		}

		private PidListViewItem[] listViewItems;
		private Color scrambledColor = Color.FromArgb(255, 255, 128, 128);
		private Color unscrambledColor = Color.FromArgb(255, 128, 255, 128);
		private void RenderPidList()
		{
			if (listViewItems == null)
				listViewItems = new PidListViewItem[0x2000];

			ulong[] ulongs = tsContext.GetPidStatistics();
			for (int i = 0; i < 0x2000; i++)
			{
				if (ulongs[i] != 0)
				{
					if (listViewItems[i] == null)
					{
						listViewItems[i] = new PidListViewItem(i);
						pidListView.Items.Add(listViewItems[i]);
					}
					listViewItems[i].SetPackageStats(ulongs[i], packetsPerSecondCounter.TotalPacketsOverall);
					listViewItems[i].BackColor = scrambleCounter.IsScrambled(i) ? scrambledColor : unscrambledColor;
				}

				if (uiJunction.PidToPrograms != null && uiJunction.SdtNames != null)
				{
					if (uiJunction.PidToPrograms.ContainsKey(i))
					{
						uint program = uiJunction.PidToPrograms[i];
						if (uiJunction.SdtNames.ContainsKey(program))
						{
							listViewItems[i].ProgramName = uiJunction.SdtNames[program];
						}
					}
				}

				if (uiJunction.UsageLabels != null)
				{
					if (uiJunction.UsageLabels.ContainsKey(i))
					{
						if (listViewItems[i] != null)
						{ 
							listViewItems[i].UsageLabel = uiJunction.UsageLabels[i];
						}
					}
				}
			}
		}

		private PidListViewItemSorter lvwColumnSorter;
		private void pidListView_ColumnClick(object sender, ColumnClickEventArgs e)
		{
			if (lvwColumnSorter == null)
			{
				lvwColumnSorter = new PidListViewItemSorter();
				pidListView.ListViewItemSorter = lvwColumnSorter;
			}

			if (e.Column == lvwColumnSorter.SortColumn)
			{
				if (lvwColumnSorter.Order == SortOrder.Ascending)
				{
					lvwColumnSorter.Order = SortOrder.Descending;
				}
				else
				{
					lvwColumnSorter.Order = SortOrder.Ascending;
				}
			}
			else
			{
				// Set the column number that is to be sorted; default to ascending.
				lvwColumnSorter.SortColumn = e.Column;
				lvwColumnSorter.Order = SortOrder.Ascending;
			}

			pidListView.Sort();
		}

		SkyscraperUiNode INodeEngine.EnsureNodeExists(params string[] path)
		{
			SkyscraperUiNode result = null;
			if (IsDisposed)
				return null;
			Invoke(new Action(() => { result = EnsureNodeExistsEx(path); }));
			return result;
		}

		private SkyscraperUiNode EnsureNodeExistsEx(params string[] path)
		{
			TreeNodeCollection rootNodes = treeView1.Nodes;

			SkyscraperUiNode parent = null;
			for (int i = 0; i < path.Length; i++)
			{
				SkyscraperUiNode foundNode = null;
				if (i == 0)
				{
					foreach (TreeNode rootNode in rootNodes)
					{
						if (rootNode.Text.Equals(path[i]))
						{
							foundNode = (SkyscraperUiNode)rootNode;
							break;
						}
					}
					if (foundNode == null)
					{
						foundNode = new SkyscraperUiNode();
						foundNode.Text = path[i];
						rootNodes.Add(foundNode);
					}
				}
				else
				{
					foreach (TreeNode childNode in parent.Nodes)
					{
						if (childNode.Text.Equals(path[i]))
						{
							foundNode = (SkyscraperUiNode)childNode;
							break;
						}
					}
					if (foundNode == null)
					{
						foundNode = new SkyscraperUiNode();
						foundNode.Text = path[i];
						parent.Nodes.Add(foundNode);
					}
				}
				parent = foundNode;
			}
			return parent;
		}

		public void RunOnUiThread(Action action)
		{
			Invoke(action);
		}

		private void treeView1_AfterSelect_1(object sender, TreeViewEventArgs e)
		{
			SkyscraperUiNode node = e.Node as SkyscraperUiNode;
			if (node == null)
				return;
			textBox1.Text = node.DetailedText;
		}

		public bool TestForNode(params string[] path)
		{
			TreeNodeCollection rootNodes = treeView1.Nodes;

			SkyscraperUiNode parent = null;
			for (int i = 0; i < path.Length; i++)
			{
				SkyscraperUiNode foundNode = null;
				if (i == 0)
				{
					foreach (TreeNode rootNode in rootNodes)
					{
						if (rootNode.Text.Equals(path[i]))
						{
							foundNode = (SkyscraperUiNode)rootNode;
							break;
						}
					}
					if (foundNode == null)
					{
						return false;
					}
				}
				else
				{
					foreach (TreeNode childNode in parent.Nodes)
					{
						if (childNode.Text.Equals(path[i]))
						{
							foundNode = (SkyscraperUiNode)childNode;
							break;
						}
					}
					if (foundNode == null)
					{
						return false;
					}
				}
				parent = foundNode;
			}
			return true;
		}

		void INodeEngine.SetIcon(SkyscraperUiNode node, int index)
		{
			RunOnUiThread(() => { 
				node.ImageIndex = index;
				node.SelectedImageIndex = index;
			});
		}
	}
}
