// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Configuration.Install;
using System.Collections;

namespace CloudStack.Plugin.AgentShell
{
    static class Program
    {
        private static ILog logger = LogManager.GetLogger(typeof(Program));
        public const string serviceName = "CloudStack Hyper-V Agent";
        private static string option = null;
        private static string user = null;
        private static string password = null;
        private const string install = "--install";
        private const string uninstall = "--uninstall";
        private const string console = "--console";

        /// <summary>
        /// Application entry point allows service to run in console application or as a Windows service.
        /// Add '--console' to the commandline for the former, the latter is the default.
        /// </summary>
        static void Main(params string[] args)
        {
            if (args.Length == 0)
            {
                logger.InfoFormat(serviceName + " running as Windows Service");
                ServiceBase[] ServicesToRun = new ServiceBase[] { new AgentService() };
                ServiceBase.Run(ServicesToRun);
            }
            else if (ParseArguments(args))
            {
                switch (option)
                {
                    case install:
                        logger.InfoFormat("Installing and running " + serviceName);
                        InstallService();
                        StartService();
                        break;
                    case uninstall:
                        logger.InfoFormat("Stopping and uninstalling " + serviceName);
                        StopService();
                        UninstallService();
                        break;
                    case console:
                        logger.InfoFormat(serviceName + " is running as console application");
                        new AgentService().RunConsole(args);
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
            else
            {
                string argumentExample = "--(install/uninstall/console) [-u <hyper-v admin user>] [-p <hyper-v admin password>].\n" +
                    " For example:\n " +
                    " --install -u domain1\\user1 -p mypwd\n"+
                    " --uninstall";
                logger.Error("Invalid arguments passed for installing\\uninstalling the service. \n" + argumentExample);
            }
        }

        static private Boolean ParseArguments(string[] args)
        {
            logger.DebugFormat(serviceName + " arg is ", args[0]);
            int argIndex = 0;
            while (argIndex < args.Length)
            {
                switch (args[argIndex])
                {
                    case install:
                    case uninstall:
                    case console:
                        option = args[argIndex];
                        argIndex++;
                        break;
                    case "-u":
                        user = args[argIndex+1];
                        argIndex+=2;
                        break;
                    case "-p":
                        password = args[argIndex+1];
                        argIndex+=2;
                        break;
                    default:
                        argIndex++;
                        // Unrecognised argument. Do nothing;
                        break;
                }
            }

            // Validate arguments
            return ValidateArguments();
        }

        private static Boolean ValidateArguments()
        {
            Boolean argsValid = false;
            switch (option)
            {
                case uninstall:
                case install:
                case console:
                    argsValid = true;
                    break;
                default:
                    break;
            }

            return argsValid;
        }

        public static string GetPassword()
        {
            return password;
        }

        public static string GetUser()
        {
            return user;
        }

        private static bool IsInstalled()
        {
            using (ServiceController controller =
                new ServiceController(serviceName))
            {
                try
                {
                    ServiceControllerStatus status = controller.Status;
                }
                catch
                {
                    return false;
                }
                return true;
            }
        }

        private static bool IsRunning()
        {
            using (ServiceController controller =
                new ServiceController(serviceName))
            {
                if (!IsInstalled()) return false;
                return (controller.Status == ServiceControllerStatus.Running);
            }
        }

        private static AssemblyInstaller GetInstaller()
        {
            AssemblyInstaller installer = new AssemblyInstaller(
                typeof(Program).Assembly, null);
            installer.UseNewContext = true;
            return installer;
        }

        private static void InstallService()
        {
            if (IsInstalled()) return;

            try
            {
                using (AssemblyInstaller installer = GetInstaller())
                {
                    IDictionary state = new Hashtable();
                    try
                    {
                        installer.Install(state);
                        installer.Commit(state);
                    }
                    catch
                    {
                        try
                        {
                            installer.Rollback(state);
                        }
                        catch { }
                        throw;
                    }
                }
            }
            catch (Exception ex)
            {
                logger.ErrorFormat(" Error occured in installing service " + ex.Message);
                throw;
            }
        }

        private static void UninstallService()
        {
            if (!IsInstalled()) return;
            try
            {
                using (AssemblyInstaller installer = GetInstaller())
                {
                    IDictionary state = new Hashtable();
                    try
                    {
                        installer.Uninstall(state);
                    }
                    catch
                    {
                        throw;
                    }
                }
            }
            catch (Exception ex)
            {
                logger.ErrorFormat(" Error occured in uninstalling service " + ex.Message);
                throw;
            }
        }

        private static void StartService()
        {
            if (!IsInstalled()) return;

            using (ServiceController controller =
                new ServiceController(serviceName))
            {
                try
                {
                    if (controller.Status != ServiceControllerStatus.Running)
                    {
                        controller.Start();
                        controller.WaitForStatus(ServiceControllerStatus.Running,
                            TimeSpan.FromSeconds(10));
                    }
                }
                catch (Exception ex)
                {
                    logger.ErrorFormat(" Error occured in starting service " + ex.Message);
                    throw;
                }
            }
        }

        private static void StopService()
        {
            if (!IsInstalled()) return;
            using (ServiceController controller =
                new ServiceController(serviceName))
            {
                try
                {
                    if (controller.Status != ServiceControllerStatus.Stopped)
                    {
                        controller.Stop();
                        controller.WaitForStatus(ServiceControllerStatus.Stopped,
                             TimeSpan.FromSeconds(10));
                    }
                }
                catch (Exception ex)
                {
                    logger.ErrorFormat(" Error occured in stopping service " + ex.Message);
                    throw;
                }
            }
        }
    }
}
