using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace AddisonWesley.Michaelis.EssentialCSharp.Shared
{
    public class Program
    {
        public static void Main(string[] args)
        {
            string listing;
            IEnumerable<string> stringArguments = null;
            if (args.Length == 0)
            {
                Console.Write("Podaj numer listingu do wykonania (np. dla listingu 18.1 wpisz \"18.1\"): ");
                listing = Console.ReadLine();
            }
            else
            {
                listing = args[0];
                stringArguments = args.Skip(1);
            }

            Console.WriteLine();
            Console.WriteLine("____________________________");
            Console.WriteLine();
            ConsoleColor originalColor = Console.ForegroundColor;

            try
            {
                string chapterName = "";
                listing = ParseListingName(listing, out chapterName);

                var assembly = Assembly.Load(new AssemblyName(chapterName));

                Type target = assembly.GetTypes().First(type => type.FullName.Contains(listing + "."));
                var method = (MethodInfo)target.GetMember("Main").First();

                object[] arguments;
                if (!method.GetParameters().Any())
                {
                    arguments = null;
                }
                else
                {
                    if (stringArguments == null)
                    {
                        arguments = new object[] { GetArguments() };
                    }
                    else
                    {
                        arguments = new object[] { stringArguments.ToArray() };
                    }
                }
                if (method.GetCustomAttributes(typeof(STAThreadAttribute), false).Any())
                {
                    Thread thread = new Thread(() => method.Invoke(null, arguments));
                    //thread.SetApartmentState(ApartmentState.STA);
                    thread.Start();
                    thread.Join();
                }
                else
                {
                    method.Invoke(null, arguments);
                }
            }
            catch (TargetParameterCountException exception)
            {
                throw new InvalidOperationException(
                    string.Format("Błąd krytyczny w trakcie wykonywania listingu {0}.\n", listing),
                        exception);
            }
            catch (InvalidOperationException)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("----Wyjątek----");
                Console.WriteLine(string.Format("Błąd, nie można uruchomić listingu '{0}'. Upewnij się, że podałeś poprawny listing we właściwym formacie.", listing));
            }
            catch (Exception exception)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("----Wyjątek----");
                if (exception.InnerException != null)
                {
                    // Typ ExceptionDispatchInfo jest stosowany z użyciem refleksji, ponieważ  
                    // nie istniał w wersji .NET 4.0 i starszych, a chcemy zachować zgodność z 
                    // tymi wersjami i jednocześnie korzystać z tego typu, gdy jest dostępny.
                    Type exceptionDispatchInfoType =
                            Type.GetType(
                                "System.Runtime.ExceptionServices.ExceptionDispatchInfo");
                    if (exceptionDispatchInfoType != null)
                    {
                        dynamic exceptionDispatchInfo = exceptionDispatchInfoType.GetMethod("Capture")
                            .Invoke(exceptionDispatchInfoType, new object[] { exception.InnerException });
                        exceptionDispatchInfo.Throw();
                    }
                    else
                        throw exception.InnerException;
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine(string.Format("Listing {0} zgłosił wyjątek typu {1}.", listing, exception.GetType()));
                }
            }
            finally
            {
                Console.ForegroundColor = originalColor;

                Console.WriteLine();
                Console.WriteLine("____________________________");
                Console.WriteLine("Koniec listingu " + listing);
                Console.Write("Wciśnij dowolny klawisz, aby kontynuować.");
                Console.ReadKey();
            }
        }

        private static string[] GetArguments()
        {
            string[] args;

            Console.WriteLine();
            Console.WriteLine(
                "Listing wymaga argumentów metody Main podawanych przez użytkownika. Sprawdź listing i podaj argumenty lub wciśnij enter, aby przekazać null: ");
            string userArguments = Console.ReadLine();
            Console.WriteLine();
            Console.WriteLine();

            if (userArguments != null)
            {
                userArguments = userArguments.Trim();
            }

            if (string.IsNullOrWhiteSpace(userArguments))
            {
                args = new string[0];
            }
            else
            {
                args = userArguments.Split(new[] { ' ' });
            }
            return args;
        }

        private static string ParseListingName(string listing, out string chapterName)
        {
            var appendices = new List<string> { "A", "B", "C", "D" };

            chapterName = "";

            string[] chapterListing = listing.Split('.');
            listing = string.Empty;

            int startPosition;

            if (!int.TryParse(chapterListing[0], out startPosition))
            {
                startPosition = 1;
                listing += chapterListing[0].ToUpper() + ".";
                chapterName = "Chapter" + (appendices.Contains(chapterListing[0].ToUpper()) ? "App" : "") + chapterListing[0];
            }
            else
            {
                startPosition = 0;
            }

            for (int index = startPosition; index < chapterListing.Length; index++)
            {
                if (index == startPosition && string.IsNullOrEmpty(chapterName)) chapterName = "Chapter" + chapterListing[index].PadLeft(2, '0');
                listing += chapterListing[index].PadLeft(2, '0') + ".";
            }

            listing = listing.Substring(0, listing.Length - 1);
            return listing.Replace('.', '_');
        }
    }
}
