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;
				}
			}
		}
	}
}
