// 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.Reflection;
using System.Text;

using Hiperscan.Common.IO;
using Hiperscan.Unix;

namespace Hiperscan.Common.LogWriter
{

    public partial class LogWriter : System.IO.TextWriter, System.IDisposable
    {
        public static LogWriter Current { get; set; }
        
        public LogWriter(string log_file, double max_size_in_mb)
        {
            this.MaxSize = max_size_in_mb;
            
            string file = Path.GetFileNameWithoutExtension(log_file);
            string extension = Path.GetExtension(log_file);
            
            string path = Path.GetDirectoryName(log_file);
            string log_1 = Path.Combine(path, $"{file}_1{extension}");
            string log_2 = Path.Combine(path, $"{file}_2{extension}");
            
            this.LogFile_1 = new FileInfo(log_1);
            this.LogFile_2 = new FileInfo(log_2);
            
            this.GetCurrentLogFile();
            this.CreateLogFileDirectory();
            
            this.ConsoleOutput = Console.Out;
            this.OpenFileStream(true);
            
            LogWriter.Current = this;
        }
        
        private void OpenFileStream(bool add_header)
        {
            lock (this)
            {
                try
                {
                    this.FileOutput = new Timestamper(this.CurrentLogFile.FullName, true, Encoding.UTF8);
                    System.Threading.Thread.Sleep(50);
                    this.FileStreamIsOpen = true;

                    if (add_header == true)
                    {
                        this.FileOutput.Disabled = true;
                        this.WriteLine("\n\n\n\n");
                        this.WriteLine("==========================================================================");
                        this.WriteLine($"Application start-up: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
                        this.WriteLine("==========================================================================");
                        this.FileOutput.Disabled = false;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("LogWriter: Could not open file stream: {0}", ex);
                    this.FileStreamIsOpen = false;
                }
            }
        }
        
        private void CloseFileStream()
        {
            lock (this)
            {
                try
                {
                    this.FileStreamIsOpen = false;
                    System.Threading.Thread.Sleep(50);
                    this.FileOutput.Close();
                }
                catch (Exception)
                {
                    this.FileStreamIsOpen = false;
                }
            }
        }
        
        public bool ZipLogFiles(string archive_name, bool over_write)
        {
            bool success = true;

            try
            {
                this.CloseFileStream();
                var files = new List<FileInfo>();
                this.LogFile_1.Refresh();
                if (this.LogFile_1.Exists == true)
                    files.Add(this.LogFile_1);

                this.LogFile_2.Refresh();
                if (this.LogFile_2.Exists == true)
                    files.Add(this.LogFile_2);

                var zip = new Zip(archive_name, over_write);
                zip.Add(files);
                zip.Finish();
            }
            catch(Exception ex)
            {
                success = false;
                Console.WriteLine("LogWriter: Could not zip log file: {0}", ex);
            }
            finally
            {
                this.OpenFileStream(false);
            }

            return success;
        }

        public Dictionary<string, byte[]> GetLogFiles()
        {
            Dictionary<string, byte[]> log_files = new Dictionary<string, byte[]>();
            try
            {
                this.CloseFileStream();
                var existing_files = new List<FileInfo>();
                this.LogFile_1.Refresh();
                if (this.LogFile_1.Exists == true)
                    existing_files.Add(this.LogFile_1);

                this.LogFile_2.Refresh();
                if (this.LogFile_2.Exists == true)
                    existing_files.Add(this.LogFile_2);

                foreach(FileInfo log_file in existing_files)
                {
                    using (FileStream fs = new FileStream(log_file.FullName, FileMode.Open, FileAccess.Read))
                    {
                        byte[] bytes = new byte[fs.Length];
                        fs.Read(bytes, 0, (int)fs.Length);
                        log_files.Add(log_file.Name, bytes);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("LogWriter: Could not zip log file: {0}", ex);
            }
            finally
            {
                this.OpenFileStream(false);
            }

            return log_files;
        }

        private void GetCurrentLogFile()
        {
            if (this.LogFile_1.Exists == false)
            {
                this.CurrentLogFile = this.LogFile_1;
                return;
            }
            if (this.LogFile_1.Exists == true)
            {
                double size = this.LogFile_1.Length / 1024.0 / 1024.0;
                if (size < this.MaxSize)
                {
                    this.CurrentLogFile = this.LogFile_1;
                    return;
                }
            }
            
            if (this.LogFile_2.Exists == false)
            {
                this.CurrentLogFile = this.LogFile_2;
                return;
            }
            
            if (this.LogFile_2.Exists == true)
            {
                double size = this.LogFile_2.Length / 1024.0 / 1024.0;
                if (size < this.MaxSize)
                {
                    this.CurrentLogFile = this.LogFile_2;
                    return;
                }
            }
            
            if (this.LogFile_1.Exists == true && this.LogFile_2.Exists == true)
            {
                if (this.LogFile_1.LastWriteTime < this.LogFile_2.LastWriteTime)
                {
                    this.LogFile_1.Delete();
                    this.CurrentLogFile = this.LogFile_1;
                }
                else
                {
                    this.LogFile_2.Delete();
                    this.CurrentLogFile = this.LogFile_2;
                }
            }
        }
        
        private void CreateLogFileDirectory()
        {
            if (this.CurrentLogFile.Exists == false)
            {
                FileIO.CreateDirectory(this.CurrentLogFile.DirectoryName);
            }
        }

        public override string ToString ()
        {
            return this.CurrentLogFile.FullName;
        }

        public override void Write(char[] buffer, int index, int count)
        {
            lock (this)
            {
                if(this.FileStreamIsOpen == true)
                    this.FileOutput.Write(buffer, index, count);

                this.ConsoleOutput.Write(buffer, index, count);
            }
        }


        public override void Write(char value)
        {
            lock (this)
            {
                if (this.FileStreamIsOpen == true)
                    this.FileOutput.Write(value);

                this.ConsoleOutput.Write(value);
            }
        }

        private FileInfo LogFile_1       { get; set; }
        private FileInfo LogFile_2       { get; set; }
                
        private FileInfo CurrentLogFile  { get; set; }
        private double MaxSize           { get; set; }
        
        private TextWriter ConsoleOutput { get; set; }
        private Timestamper FileOutput   { get; set; }
        private bool FileStreamIsOpen    { get; set; }
    

        #region IDisposable implementation
        public new void Dispose ()
        {
            try
            {
                this.CloseFileStream();
            }
#pragma warning disable RECS0022
            catch (Exception) { }
#pragma warning restore RECS0022

            base.Dispose();
        }
        #endregion

        public static bool ConsoleIsRedirected
        {
            get
            {
                var writer = Console.Out;

                if (writer == null || writer.GetType().FullName != "System.IO.TextWriter+SyncTextWriter")
                    return true;

                var fld = writer.GetType().GetField("_out", BindingFlags.Instance | BindingFlags.NonPublic);
                if (fld == null)
                    return true;

                if (!(fld.GetValue(writer) is StreamWriter streamWriter))
                    return true;

                return streamWriter.BaseStream.GetType().FullName != "System.IO.__ConsoleStream";
            }
        }
    }
}
