// Created with MonoDevelop
//
//    Copyright (C) 2017 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Hiperscan.Unix;

namespace Hiperscan.Common.LogWriter
{

    public enum LogType
    {
        Info,
        Skip,
        Warning,
        Error,
        Critical,
        Abort
    }

    public enum LogLevelType
    {
        Strict,
        Tolerant
    }

    public partial class LogWriter
    {
        public static void Init(string logfile, bool delete, string log_name)
        {
            LogWriter.Init(new FileInfo(logfile), delete, log_name);
        }

        public static void Init(FileInfo file, bool delete, string report_name)
        {
            LogWriter.Logfile = file;
            LogWriter.WriteToStandardErr = false;
            if (LogWriter.Logfile.Exists == true && delete == true)
                LogWriter.Logfile.Delete();

            LogWriter.AbortCount = 0;
            LogWriter.InfoCount = 0;
            LogWriter.ErrorCount = 0;
            LogWriter.WarningCount = 0;
            LogWriter.CriticalCount = 0;

            FileIO.CreateDirectory(LogWriter.Logfile.Directory.FullName);

            LogWriter.TimeFormat = "yyyy-MM-dd HH:mm:ss";

            LogWriter.Sync = new Semaphore(1, 1);
            Exception ex = null;
            LogWriter.Sync.WaitOne();
            {
                try
                {
                    using (StreamWriter sw = new StreamWriter(LogWriter.Logfile.FullName, true, Encoding.UTF8))
                    {
                        sw.WriteLine("{0} logfile, {1}", report_name, DateTime.Now.ToString(LogWriter.TimeFormat));
                        sw.WriteLine("[I] = Info, [S] = Skip, [W] = Warning, [E] = Error, [C] = Critical, [A] = Abort");
                        sw.WriteLine();
                    }
                }
                catch (Exception _ex)
                {
                    ex = _ex;
                }
            }
            LogWriter.Sync.Release();

            if (ex != null)
                throw ex;
        }

        public static void WriteHeader(IEnumerable<string> arguments)
        {
            LogWriter.AddEntry(LogType.Info, $"Assembly version: {Misc.AssemblyVersion}");
            LogWriter.AddEntry(LogType.Info, $"Arguments: {string.Join(" ", Misc.MaskPasswords(arguments))}");
            LogWriter.AddEntry(LogType.Info, $"Toolchain Environment: {Environment.GetEnvironmentVariable("TOOLCHAIN_ENV") ?? "n/a"}" );
            LogWriter.AddEntry(LogType.Info, $"Mono version: {Misc.MonoVersion}");
            LogWriter.AddEntry(LogType.Info, $"Hostname: {Environment.MachineName}");
            LogWriter.AddEntry(LogType.Info, $"Username: {Environment.UserName}");
            LogWriter.AddEntry(LogType.Info, $"Current working directory: {Directory.GetCurrentDirectory()}");
            if (OperatingSystem.IsDebian())
                LogWriter.AddEntry(LogType.Info, $"Debian version: {OperatingSystem.GetDebianVersion()}");
        }

        public static void AddEntry(LogType type, string message)
        {
            LogWriter.AddEntry(type, message, false);
        }

        public static void AddEntry(LogType type, string message, bool console)
        {
            LogWriter.AddEntry(type, "GLOBAL", null, null, message, console);
        }

        public static void AddEntry(LogType type, string id, string id_description, string id_explanation, string message)
        {
            LogWriter.AddEntry(type, id, id_description, id_explanation, message, false);
        }

        public static void AddEntry(LogType type, string id, string id_description, string id_explanation, string message, bool console)
        {
            if (LogWriter.Logfile == null && console == false)
                return;

            string message_type = string.Format("[{0}]", type.ToString()[0]);
            string time = string.Format("[{0}]", DateTime.Now.ToString(LogWriter.TimeFormat));

            id = string.IsNullOrEmpty(id) == false ? string.Format("[{0}]", id) : null;
            id_description = string.IsNullOrEmpty(id_description) == false ? string.Format("{0}", id_description) : null;
            id_explanation = string.IsNullOrEmpty(id_explanation) == false ? string.Format("<<{0}>>", id_explanation) : null;
            message = string.IsNullOrEmpty(message) == false ? string.Format(":: {0}", message) : null;

            string line = string.Format("{0} {1}", message_type, time);

            if (id != null)
                line += string.Format(" {0}", id.Replace("\n", " ").Trim());

            if (id_description != null)
                line += string.Format(" {0}", id_description.Replace("\n", " ").Trim());

            if (id_explanation != null)
                line += string.Format(" {0}", id_explanation.Replace("\n", " ").Trim());

            if (message != null)
                line += string.Format("{0}", message.Replace("\n", " "));

            LogWriter.Sync?.WaitOne();

            try
            {
                switch (type)
                {

                case LogType.Abort:
                    LogWriter.AbortCount++;
                    break;

                case LogType.Critical:
                    LogWriter.CriticalCount++;
                    break;

                case LogType.Error:
                    LogWriter.ErrorCount++;
                    break;

                case LogType.Info:
                    LogWriter.InfoCount++;
                    break;

                case LogType.Warning:
                    LogWriter.WarningCount++;
                    break;

                case LogType.Skip:
                    LogWriter.SkipCount++;
                    break;

                default:
                    break;

                }

                if (LogWriter.Logfile != null)
                {
                    using (StreamWriter sw = new StreamWriter(LogWriter.Logfile.FullName, true, Encoding.UTF8))
                    {
                        sw.WriteLine(line);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(Catalog.GetString("Cannot write log entry:") + " " + ex.Message, ex);
            }
            finally
            {
                LogWriter.Sync?.Release();
            }

            if ((type == LogType.Abort || console == true) && LogWriter.Quiet == false)
            {
                message = message ?? String.Empty;
                if (message.StartsWith(":: ", StringComparison.InvariantCulture))
                    message = message.Substring(3);

                if (LogWriter.WriteToStandardErr == true)
                    Console.Error.WriteLine(message.TrimStart(new char[] { ':' }));
                else
                    Console.WriteLine(message.TrimStart(new char[] { ':' }));
            }                
        }

        public static void WriteSummary()
        {
            if (LogWriter.Logfile == null)
                return;

            Exception e = null;
            LogWriter.Sync.WaitOne();
            {
                try
                {
                    using (StreamWriter sw = new StreamWriter(LogWriter.Logfile.FullName, true, Encoding.UTF8))
                    {
                        sw.WriteLine();
                        sw.WriteLine("Statistics: [I] = {0}, [S] = {1}, [W] = {2}, [E] = {3}, [C] = {4}, [A] = {5}",
                                     LogWriter.InfoCount, LogWriter.SkipCount, LogWriter.WarningCount, 
                                     LogWriter.ErrorCount, LogWriter.CriticalCount, LogWriter.AbortCount);
                    }
                }
                catch (Exception ex)
                {
                    e = ex;
                }
            }
            LogWriter.Sync.Release();

            if (e != null)
                throw new Exception(Catalog.GetString("Cannot write log summary."), e);
        }

        public static void Concat(string file)
        {
            if (LogWriter.Logfile == null)
                return;

            if (false == FileIO.ExistsFile(file))
                return;

            Exception e = null;
            LogWriter.Sync.WaitOne();
            {
                try
                {
                    using (StreamWriter sw = new StreamWriter(LogWriter.Logfile.FullName, true, Encoding.UTF8))
                    {
                        sw.WriteLine();
                        sw.WriteLine();
                        sw.WriteLine("====== concated logfile: {0} =======", file);
                        sw.WriteLine();

                        string line = null;

                        using (StreamReader sr = new StreamReader(file, Encoding.UTF8))
                        {
                            while ((line = sr.ReadLine()) != null)
                                sw.WriteLine(line);
                                
                        }
                    }
                }
                catch (Exception ex)
                {
                    e = ex;
                }
            }
            LogWriter.Sync.Release();

            if (e != null)
                throw new Exception("Cannot concat file.", e);

        }

        public static FileInfo Logfile        { get; set; }
        public static bool WriteToStandardErr { get; set; }
        private static Semaphore Sync         { get; set; }
        private static string TimeFormat      { get; set; }

        public static int InfoCount     { get; private set; }
        public static int SkipCount     { get; private set; }
        public static int WarningCount  { get; private set; }
        public static int CriticalCount { get; private set; }
        public static int ErrorCount    { get; private set; }
        public static int AbortCount    { get; private set; }

        public static bool Quiet { get; set; }

        public static LogLevelType LogLevel { get; set; } = LogLevelType.Strict;

        public override Encoding Encoding => this.ConsoleOutput.Encoding;
    }
}
