//
// Initializer.cs
//
// Author:
//       Carsten Sonne Larsen <cs@innolan.dk>
//
// Copyright (c) 2013-2016 Carsten Sonne Larsen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using Ntp.Analyzer.Config;
using Ntp.Analyzer.Config.Graph;
using Ntp.Analyzer.Config.Page;
using Ntp.Analyzer.Data;
using Ntp.Analyzer.Log;
using Ntp.Config;
using Ntp.Monitor.Server;
using Ntp.Process;
using Ntp.Analyzer.Process.Description;
using Ntp.Analyzer.Config.Root;
using Ntp.Monitor.Client;
using System.Net;
using Ntp.System;
using Ntp.Analyzer.Objects;
using System.Linq;
using Ntp.Analyzer.Objects.Live;

namespace Ntp.Analyzer.Process
{
    public sealed class Initializer
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Ntp.Analyzer.Process.Runner" /> class.
        /// </summary>
        /// <param name="configFile">Config file to use.</param>
        /// <param name="pid">Process ID. Only used for logging.</param>
        /// <param name="name">Name of process ID. Used for shutting down.</param>
        public Initializer (string version, string configFile, int pid, string name)
        {
            this.version = version;
            this.configFile = configFile;
            this.pid = pid;
            this.name = name;

            nodes = new List<IRequest> ();
            listeners = new List<Listener> ();
        }

        private readonly string version;
        private readonly string configFile;
        private readonly int pid;
        private readonly string name;
        private readonly List<IRequest> nodes;
        private readonly List<Listener> listeners;
        private Configuration config;
        private LogBase log;
        private Scheduler scheduler;
        private bool ready;

        public Scheduler Scheduler {
            get { return scheduler; }
        }

        public LogBase Log {
            get { return log; }
        }

        public List<IRequest> Nodes {
            get { return nodes; }
        }

        public bool Ready {
            get { return ready; }
        }

        /// <summary>
        /// Run the NTP Analyzer.
        /// </summary>
        public void Run ()
        {
            // Neutralize.
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            bool proceed = InitializeConfiguration ();

            if (proceed)
                proceed = InitializeLog ();

            if (proceed) {
                InitializeState ();
                InitializeDatabase ();
                InitializeData ();
                InitializeListerners ();
                InitializeCluster ();
                InitializeScheduler ();
            }

            ready = proceed;
        }

        /// <summary>
        /// Initializes the configuration.
        /// </summary>
        private bool InitializeConfiguration ()
        {
            if (!File.Exists (configFile)) {
                Console.WriteLine ("Cannot find configuration file: " + configFile);
                return false;
            }

            // Read config.
            try {
                string[] configLines = File.ReadAllLines (configFile);
                ConfigurationBlock block = new ConfigurationBlock (configLines);
                config = Configuration.Create (block);
            } catch (Exception ex) {
                Console.WriteLine ("Cannot read configuration file: " + configFile);
                Console.WriteLine (ex.Message);
                return false;
            }

            if (ConfigurationBase.Errors != String.Empty) {
                Console.WriteLine ("Errors in configuration file");
                Console.WriteLine (ConfigurationBase.Errors);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Initializes the log.
        /// </summary>
        private bool InitializeLog ()
        {
            // Create log.
            try {
                log = LogFactory.CreateLog (config.Log);
                log.Initialize();
            } catch (Exception ex) {
                Console.WriteLine ("Cannot create log file. " + ex.Message);
                return false;
            }

            // Initialize log.
            log.WriteLine ("NTP Analyzer " + version + " started.", Severity.Notice);
            log.WriteLine ("Using configuration: " + configFile, Severity.Notice);
            log.WriteLine ("Running with pid: " + pid, Severity.Notice);

            return true;
        }

        /// <summary>
        /// Initializes the application and database state.
        /// </summary>
        private void InitializeState ()
        {
            // Initialize application state
            ApplicationState.Version = version;
            ApplicationState.Pid = pid;
            ApplicationState.Name = name;
            ApplicationState.Config = config;
            ApplicationState.ConfigFile = configFile;
            ApplicationState.Log = log;

            // Initialize system settings
            ShellCommand.WorkingDirectory = "/tmp";
        }

        private void InitializeDatabase()
        {
            // Initialize database connection
            DataConnection.DatabaseName = config.Database.Name;
            DataConnection.String = config.Database.ConnectionString;
            DataConnection.InitializeString = config.Database.InitializeConnectionString;
            DataConnection.Initialize = config.Database.Initialize;
            DataConnection.Log = log;

            // Initialize database tables
            if (config.Database.Initialize) {
                try {
                    DataInitializer initializer = new DataInitializer (log);
                    initializer.CreateDatabase ();
                } catch (Exception e) {
                    log.WriteLine ("Error during initialization of database.", Severity.Error);
                    log.WriteLine (e.Message, Severity.Debug);
                    log.WriteLine (e, Severity.Trace);
                }
            }
        }

        private void InitializeData()
        {
            if (!config.Database.Initialize)
                return;
            
            try {
                // Initialize hosts
                foreach (ServerConfiguration server in config.Servers) {
                    Host host = DataFace.Instance.Hosts.SingleOrDefault (h => h.Id == server.HostId);
                    if (host == null) {
                        IPAddress ip;
                        try {
                            ip = Dns.GetHostAddresses (server.ServerName) [0];
                        } catch {
                            ip = IPAddress.None;
                        }
                        host = new Host (server.HostId, server.ServerName, ip.ToString (), null);
                        DataFace.Instance.Hosts.Save (host);
                        Log.WriteLine (String.Format (
                            "Created a new host in database ID {0} with name {1} and IP {2}.",
                            server.HostId, server.ServerName, ip),
                            Severity.Info);
                    }

                    // Initialize peers
                    foreach (AssociationEntry entry in DataFace.Instance.NtpqCache[server.ServerName]) {
                        IEnumerable<Peer> peerList = DataFace.Instance.Peers.Where (p => p.Ip == entry.Remote);
                        if (peerList.Count () == 0) {
                            Peer peer = new Peer (entry.Remote, entry.Remote, null);
                            DataFace.Instance.Peers.Save (peer);
                            Log.WriteLine (String.Format (
                                "Created a new peer in database with name {0} and IP {1}.",
                                entry.Remote, entry.Remote),
                                Severity.Info);
                        }
                    }
                }
            } catch (Exception e) {
                log.WriteLine ("Could not populating all data into tables.", Severity.Error);
                log.WriteLine (e.Message, Severity.Debug);
                log.WriteLine (e, Severity.Trace);
            }            
        }
        
        /// <summary>
        /// Initializes the listerners.
        /// </summary>
        private void InitializeListerners ()
        {
            try {
                foreach (ListenerConfiguration monitor in config.Monitors) {
                    Listener listener = new Listener (monitor.Ip, monitor.Port, log);
                    listener.Open ();
                    listeners.Add (listener);

                    log.WriteLine ("Listening " + listener, Severity.Notice); 
                }
            } catch (Exception e) {
                log.WriteLine ("Error during initialization of listerners.", Severity.Error);
                log.WriteLine (e.Message, Severity.Debug);
                log.WriteLine (e, Severity.Trace);
            }
        }

        /// <summary>
        /// Initializes the cluster nodes.
        /// </summary>
        private void InitializeCluster ()
        {
            if (config.Cluster == null)
                return;

            try {
                foreach (NodeConfiguration node in config.Cluster.Nodes) {
                    IPAddress ip = IPAddress.Parse (node.Ip);

                    IRequest req = new TextRequest (ip, node.Port);
                    nodes.Add (req);

                    log.WriteLine ("Reqesting to cluster " + node.Address, Severity.Notice); 
                }
            } catch (Exception e) {
                log.WriteLine ("Error during initialization of cluster node.", Severity.Error);
                log.WriteLine (e.Message, Severity.Debug);
                log.WriteLine (e, Severity.Trace);
            }        
        }

        /// <summary>
        /// Initializes the scheduler.
        /// </summary>
        private void InitializeScheduler ()
        {
            try {
                scheduler = new Scheduler (log);
                log.WriteLine ("Initializing jobs.", Severity.Debug);
                scheduler.Add (new BulkStatJob (config.Bulk, scheduler.Log));

                // Add jobs to schedule.
                foreach (ServerConfiguration server in config.Servers) {
                    scheduler.Add (new HostStatJob (server.HostStats, scheduler.Log));
                    scheduler.Add (new HostIOStatJob (server.HostIoStats, scheduler.Log));
                    scheduler.Add (new PeerStatJob (server.PeerStats, scheduler.Log));
                    scheduler.Add (new AboutPageJob (server.Menu, server.AboutPage, scheduler.Log));

                    foreach (PeerPageConfiguration peerPage in server.PeerPages)
                        scheduler.Add (new PeerPageJob (server.Menu, peerPage, scheduler.Log));

                    foreach (HostPageConfiguration hostPage in server.HostPages)
                        scheduler.Add (new HostPageJob (server.Menu, hostPage, scheduler.Log));

                    foreach (HostGraphConfiguration hostGraph in server.HostGraphs)
                        scheduler.Add (new HostGraphJob (hostGraph, scheduler.Log));

                    foreach (TrafficGraphConfiguration trafficGraph in server.TrafficGraphs)
                        scheduler.Add (new TrafficGraphJob (trafficGraph, scheduler.Log));

                    foreach (PeerGraphConfiguration peerGraph in server.PeerGraphs)
                        scheduler.Add (new PeerGraphJob (peerGraph, scheduler.Log));

                    foreach (PeerSummeryPageConfiguration peerSummeryPage in server.PeerSummeryPages)
                        scheduler.Add (new PeerSummeryJob (server.Menu, peerSummeryPage, scheduler.Log));

                    scheduler.Add (new HostGraphPageJob (server.HostGraphPage, scheduler.Log));
                    scheduler.Add (new PeerGraphPageJob (server.PeerGraphPage, scheduler.Log));
                }

                // Add the survaliance/notify job.
                if (config.Notify != null) {
                    NotifyJob adminJob = new NotifyJob (config.Notify, log, pid, configFile);
                    scheduler.Add (adminJob);
                    adminJob.SetJobList (scheduler);

                    // Done
                    log.WriteLine ("All jobs initialized.", Severity.Debug);
                }
            } catch (Exception e) {
                log.WriteLine ("Error during initialization of scheduler.", Severity.Error);
                log.WriteLine (e.Message, Severity.Debug);
                log.WriteLine (e, Severity.Trace);
            }

            ApplicationState.Scheduler = scheduler;
            ApplicationState.StartupTime = scheduler.StartTime;
        }
    }
}