using System;

namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
	public abstract class ReduceNesting
	{
		public abstract bool B(int i);
		public abstract int I(int i);

		public void IfIf()
		{
			if (B(0))
			{
				Console.WriteLine(0);
				return;
			}

			if (B(1))
			{
				Console.WriteLine(1);
			}

			Console.WriteLine("end");
		}

		public void IfSwitch()
		{
			if (B(0))
			{
				Console.WriteLine(0);
				return;
			}

			Console.WriteLine("switch");
			switch (I(0))
			{
				case 0:
					Console.WriteLine("case 0");
					break;
				case 1:
					Console.WriteLine("case 1");
					break;
				default:
					Console.WriteLine("end");
					break;
			}
		}

		public void IfSwitchSwitch()
		{
			if (B(0))
			{
				Console.WriteLine(0);
				return;
			}

			Console.WriteLine("switch 0");
			switch (I(1))
			{
				case 0:
					Console.WriteLine("case 0");
					return;
				case 1:
					Console.WriteLine("case 1");
					return;
			}

			Console.WriteLine("switch 1");
			switch (I(1))
			{
				case 0:
					Console.WriteLine("case 0");
					break;
				case 1:
					Console.WriteLine("case 1");
					break;
				default:
					Console.WriteLine("end");
					break;
			}
		}

		public void IfLoop()
		{
			if (B(0))
			{
				Console.WriteLine(0);
				return;
			}

			for (int i = 0; i < 10; i++)
			{
				Console.WriteLine(i);
			}

			Console.WriteLine("end");
		}

		public void LoopContinue()
		{
			for (int i = 0; i < 10; i++)
			{
				Console.WriteLine(i);
				if (B(0))
				{
					Console.WriteLine(0);
					continue;
				}

				if (B(1))
				{
					Console.WriteLine(1);
				}
				Console.WriteLine("loop-tail");
			}
		}

		public void LoopBreak()
		{
			for (int i = 0; i < 10; i++)
			{
				Console.WriteLine(i);
				if (B(0))
				{
					Console.WriteLine(0);
					continue;
				}

				if (B(1))
				{
					Console.WriteLine(1);
					break;
				}

				if (B(2))
				{
					Console.WriteLine(2);
				}

				Console.WriteLine("break");
				break;
			}
			Console.WriteLine("end");
		}

		public void LoopBreakElseIf()
		{
			for (int i = 0; i < 10; i++)
			{
				Console.WriteLine(i);
				if (B(0))
				{
					Console.WriteLine(0);
					continue;
				}

				if (B(1))
				{
					Console.WriteLine(1);
				}
				else if (B(2))
				{
					Console.WriteLine(2);
				}
				break;
			}
			Console.WriteLine("end");
		}

		public void SwitchIf()
		{
			switch (I(0))
			{
				case 0:
					Console.WriteLine("case 0");
					return;
				case 1:
					Console.WriteLine("case 1");
					return;
			}

			if (B(0))
			{
				Console.WriteLine(0);
			}
			Console.WriteLine("end");
		}

		public void NestedSwitchIf()
		{
			if (B(0))
			{
				switch (I(0))
				{
					case 0:
						Console.WriteLine("case 0");
						return;
					case 1:
						Console.WriteLine("case 1");
						return;
				}
				if (B(1))
				{
					Console.WriteLine(1);
				}
			}
			else
			{
				Console.WriteLine("else");
			}
		}

		// nesting should not be reduced as maximum nesting level is 1
		public void EarlyExit1()
		{
			if (!B(0))
			{
				for (int i = 0; i < 10; i++)
				{
					Console.WriteLine(i);
				}
				Console.WriteLine("end");
			}
		}

		// nesting should be reduced as maximum nesting level is 2
		public void EarlyExit2()
		{
			if (B(0))
			{
				return;
			}

			for (int i = 0; i < 10; i++)
			{
				Console.WriteLine(i);
				if (i % 2 == 0)
				{
					Console.WriteLine("even");
				}
			}

			Console.WriteLine("end");
		}

		// nesting should not be reduced as maximum nesting level is 1 and the else block has no more instructions than any other block
		public void BalancedIf()
		{
			if (B(0))
			{
				Console.WriteLine("true");
				if (B(1))
				{
					Console.WriteLine(1);
				}
			}
			else
			{
				if (B(2))
				{
					Console.WriteLine(2);
				}
				Console.WriteLine("false");
			}
		}

		public string ComplexCase1(string s)
		{
			if (B(0))
			{
				return s;
			}

			for (int i = 0; i < s.Length; i++)
			{
				if (B(1))
				{
					Console.WriteLine(1);
				}
				else if (B(2))
				{
					switch (i)
					{
						case 1:
							if (B(3))
							{
								Console.WriteLine(3);
								break;
							}

							Console.WriteLine("case1");
							if (B(4))
							{
								Console.WriteLine(4);
							}
							break;
						case 2:
						case 3:
							Console.WriteLine("case23");
							break;
					}
					Console.WriteLine(2);
				}
				else if (B(5))
				{
					Console.WriteLine(5);
				}
				else
				{
					if (B(6))
					{
						Console.WriteLine(6);
					}
					Console.WriteLine("else");
				}
			}
			return s;
		}

		public void EarlyExitBeforeTry()
		{
			if (B(0))
			{
				return;
			}

			try
			{
				if (B(1))
				{
					Console.WriteLine();
				}
			}
			catch
			{
			}
		}

		public void EarlyExitInTry()
		{
			try
			{
				if (B(0))
				{
					return;
				}

				Console.WriteLine();

				if (B(1))
				{
					for (int i = 0; i < 10; i++)
					{
						Console.WriteLine(i);
					}
				}
			}
			catch
			{
			}
		}

		public void ContinueLockInLoop()
		{
			while (B(0))
			{
				lock (Console.Out)
				{
					if (B(1))
					{
						continue;
					}

					Console.WriteLine();

					if (B(2))
					{
						for (int i = 0; i < 10; i++)
						{
							Console.WriteLine(i);
						}
					}
				}
			}
		}

		public void BreakLockInLoop()
		{
			while (B(0))
			{
				lock (Console.Out)
				{
					// Before ReduceNestingTransform, the rest of the lock body is nested in if(!B(1)) with a continue;
					// the B(1) case falls through to a break outside the lock
					if (B(1))
					{
						break;
					}

					Console.WriteLine();

					if (B(2))
					{
						for (int i = 0; i < 10; i++)
						{
							Console.WriteLine(i);
						}
					}

					// the break gets duplicated into the lock (replacing the leave) making the lock 'endpoint unreachable' and the break outside the lock is removed
					// After the condition is inverted, ReduceNestingTransform isn't smart enough to then move the continue out of the lock
					// Thus the redundant continue;
					continue;
				}
			}
			Console.WriteLine();
		}

		public unsafe void BreakPinnedInLoop(int[] arr)
		{
			while (B(0))
			{
				fixed (int* ptr = arr)
				{
					if (B(1))
					{
						break;
					}

					Console.WriteLine();

					if (B(2))
					{
						for (int i = 0; i < 10; i++)
						{
							Console.WriteLine(ptr[i]);
						}
					}

					// Same reason as BreakLockInLoop
					continue;
				}
			}
			Console.WriteLine();
		}

		public void CannotEarlyExitInTry()
		{
			try
			{
				if (B(0))
				{
					Console.WriteLine();

					if (B(1))
					{
						for (int i = 0; i < 10; i++)
						{
							Console.WriteLine(i);
						}
					}
				}
			}
			catch
			{
			}
			Console.WriteLine();
		}

		public void EndpointUnreachableDueToEarlyExit()
		{
			using (Console.Out)
			{
				if (B(0))
				{
					return;
				}
				do
				{
					if (B(1))
					{
						return;
					}
				} while (B(2));
				throw new Exception();
			}
		}

		public void SwitchInTry()
		{
			try
			{
				switch (I(0))
				{
					case 1:
						Console.WriteLine(1);
						return;
					case 2:
						Console.WriteLine(2);
						return;
				}
				Console.WriteLine(3);
				for (int i = 0; i < 10; i++)
				{
					Console.WriteLine(i);
				}
			}
			catch
			{
				throw;
			}
		}

		public void SwitchInTryInLoopReturn()
		{
			for (int i = 0; i < 10; i++)
			{
				try
				{
					switch (I(0))
					{
						case 1:
							Console.WriteLine(1);
							return;
						case 2:
							Console.WriteLine(2);
							return;
					}
					Console.WriteLine(3);
					for (int j = 0; j < 10; j++)
					{
						Console.WriteLine(j);
					}
				}
				catch
				{
					throw;
				}
			}
		}

		public void SwitchInTryInLoopContinue()
		{
			for (int i = 0; i < 10; i++)
			{
				try
				{
					switch (I(0))
					{
						case 1:
							Console.WriteLine(1);
							continue;
						case 2:
							Console.WriteLine(2);
							continue;
					}
					Console.WriteLine(3);
					for (int j = 0; j < 10; j++)
					{
						Console.WriteLine(j);
					}
				}
				catch
				{
					throw;
				}
			}
		}
	}
}
