using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO;
using System.Web.Mvc;

namespace Disco.Services.Logging
{
    public static class Utilities
    {

        public const string LogEventCSVHeader = "Timestamp,ModuleId,ModuleName,ModuleDescription,EventTypeId,EventTypeName,Severity,Message";
        public static void ToCsvLine(this Models.LogLiveEvent e, TextWriter writer)
        {
            writer.Write(e.Timestamp.ToString("yyy-MM-dd HH:mm:ss"));
            writer.Write(",");
            writer.Write(e.ModuleId);
            writer.Write(",\"");
            writer.Write(e.ModuleName);
            writer.Write("\",\"");
            writer.Write(e.ModuleDescription);
            writer.Write("\",");
            writer.Write(e.EventTypeId);
            writer.Write(",\"");
            writer.Write(e.EventTypeName);
            writer.Write("\",");
            writer.Write(e.EventTypeSeverity);
            writer.Write(",\"");
            writer.Write(e.FormattedMessage.Replace("\"", "'"));
            writer.Write("\"");
            if (e.Arguments != null)
            {
                foreach (var arg in e.Arguments)
                {
                    writer.Write(",\"");
                    if (arg == null)
                        writer.Write("null");
                    else
                        writer.Write(arg.ToString().Replace("\"", "'"));
                    writer.Write("\"");
                }
            }
            writer.WriteLine();
        }
        public static MemoryStream ToCsv(this List<Models.LogLiveEvent> e)
        {
            var ms = new MemoryStream();
            StreamWriter sw = new StreamWriter(ms);
            sw.WriteLine(LogEventCSVHeader);
            if (e != null)
            {
                foreach (var le in e)
                {
                    le.ToCsvLine(sw);
                }
            }
            sw.Flush();
            ms.Position = 0;
            return ms;
        }

        public static List<SelectListItem> ToSelectListItems(this List<Models.LogEventType> items)
        {
            return items.Select(et => new SelectListItem() { Value = et.Id.ToString(), Text = et.Name }).ToList();
        }

        #region Win32 APIs
        /// <summary>
        /// The CreateFile function creates or opens a file, file stream, directory, physical disk, volume, console buffer, tape drive,
        /// communications resource, mailslot, or named pipe. The function returns a handle that can be used to access an object.
        /// </summary>
        /// <param name="lpFileName"></param>
        /// <param name="dwDesiredAccess"> access to the object, which can be read, write, or both</param>
        /// <param name="dwShareMode">The sharing mode of an object, which can be read, write, both, or none</param>
        /// <param name="SecurityAttributes">A pointer to a SECURITY_ATTRIBUTES structure that determines whether or not the returned handle can 
        /// be inherited by child processes. Can be null</param>
        /// <param name="dwCreationDisposition">An action to take on files that exist and do not exist</param>
        /// <param name="dwFlagsAndAttributes">The file attributes and flags. </param>
        /// <param name="hTemplateFile">A handle to a template file with the GENERIC_READ access right. The template file supplies file attributes 
        /// and extended attributes for the file that is being created. This parameter can be null</param>
        /// <returns>If the function succeeds, the return value is an open handle to a specified file. If a specified file exists before the function 
        /// all and dwCreationDisposition is CREATE_ALWAYS or OPEN_ALWAYS, a call to GetLastError returns ERROR_ALREADY_EXISTS, even when the function 
        /// succeeds. If a file does not exist before the call, GetLastError returns 0 (zero).
        /// If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.
        /// </returns>
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        private static extern SafeFileHandle CreateFile(
              string lpFileName,
              EFileAccess dwDesiredAccess,
              EFileShare dwShareMode,
              IntPtr SecurityAttributes,
              ECreationDisposition dwCreationDisposition,
              EFileAttributes dwFlagsAndAttributes,
              IntPtr hTemplateFile
              );
        [Flags]
        private enum EFileAccess : uint
        {
            Delete = 0x10000,
            ReadControl = 0x20000,
            WriteDAC = 0x40000,
            WriteOwner = 0x80000,
            Synchronize = 0x100000,

            StandardRightsRequired = 0xF0000,
            StandardRightsRead = ReadControl,
            StandardRightsWrite = ReadControl,
            StandardRightsExecute = ReadControl,
            StandardRightsAll = 0x1F0000,
            SpecificRightsAll = 0xFFFF,

            AccessSystemSecurity = 0x1000000,       // AccessSystemAcl access type

            MaximumAllowed = 0x2000000,         // MaximumAllowed access type

            GenericRead = 0x80000000,
            GenericWrite = 0x40000000,
            GenericExecute = 0x20000000,
            GenericAll = 0x10000000
        }
        [Flags]
        private enum EFileShare : uint
        {
            /// <summary>
            /// 
            /// </summary>
            None = 0x00000000,
            /// <summary>
            /// Enables subsequent open operations on an object to request read access. 
            /// Otherwise, other processes cannot open the object if they request read access. 
            /// If this flag is not specified, but the object has been opened for read access, the function fails.
            /// </summary>
            Read = 0x00000001,
            /// <summary>
            /// Enables subsequent open operations on an object to request write access. 
            /// Otherwise, other processes cannot open the object if they request write access. 
            /// If this flag is not specified, but the object has been opened for write access, the function fails.
            /// </summary>
            Write = 0x00000002,
            /// <summary>
            /// Enables subsequent open operations on an object to request delete access. 
            /// Otherwise, other processes cannot open the object if they request delete access.
            /// If this flag is not specified, but the object has been opened for delete access, the function fails.
            /// </summary>
            Delete = 0x00000004
        }
        private enum ECreationDisposition : uint
        {
            /// <summary>
            /// Creates a new file. The function fails if a specified file exists.
            /// </summary>
            New = 1,
            /// <summary>
            /// Creates a new file, always. 
            /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, 
            /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
            /// </summary>
            CreateAlways = 2,
            /// <summary>
            /// Opens a file. The function fails if the file does not exist. 
            /// </summary>
            OpenExisting = 3,
            /// <summary>
            /// Opens a file, always. 
            /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
            /// </summary>
            OpenAlways = 4,
            /// <summary>
            /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
            /// The calling process must open the file with the GENERIC_WRITE access right. 
            /// </summary>
            TruncateExisting = 5
        }
        [Flags]
        private enum EFileAttributes : uint
        {
            None = 0x0000000,
            Readonly = 0x00000001,
            Hidden = 0x00000002,
            System = 0x00000004,
            Directory = 0x00000010,
            Archive = 0x00000020,
            Device = 0x00000040,
            Normal = 0x00000080,
            Temporary = 0x00000100,
            SparseFile = 0x00000200,
            ReparsePoint = 0x00000400,
            Compressed = 0x00000800,
            Offline = 0x00001000,
            NotContentIndexed = 0x00002000,
            Encrypted = 0x00004000,
            Write_Through = 0x80000000,
            Overlapped = 0x40000000,
            NoBuffering = 0x20000000,
            RandomAccess = 0x10000000,
            SequentialScan = 0x08000000,
            DeleteOnClose = 0x04000000,
            BackupSemantics = 0x02000000,
            PosixSemantics = 0x01000000,
            OpenReparsePoint = 0x00200000,
            OpenNoRecall = 0x00100000,
            FirstPipeInstance = 0x00080000
        }

        private const int FSCTL_SET_COMPRESSION = 0x9C040;
        private const short COMPRESSION_FORMAT_DEFAULT = 1;
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern int DeviceIoControl(
            SafeFileHandle hDevice,
            int dwIoControlCode,
            ref short lpInBuffer,
            int nInBufferSize,
            IntPtr lpOutBuffer,
            int nOutBufferSize,
            ref int lpBytesReturned,
            IntPtr lpOverlapped);
        #endregion

        public static void CompressDirectory(string DirectoryPath)
        {
            if (DirectoryPath.Length > 250)
                throw new InvalidOperationException(string.Format("Directory Path to Long (>250) to Compress: {0}", DirectoryPath));

            DirectoryInfo dirInfo = new DirectoryInfo(DirectoryPath);
            if (dirInfo.Exists)
            {
                if ((dirInfo.Attributes & FileAttributes.Compressed) != FileAttributes.Compressed)
                {
                    var dirHandle = CreateFile(DirectoryPath, EFileAccess.GenericWrite, EFileShare.Read, IntPtr.Zero, ECreationDisposition.OpenExisting, EFileAttributes.None, IntPtr.Zero);
                    if (dirHandle.IsInvalid)
                    {
                        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                    }
                    else
                    {
                        EnableCompression(dirHandle);
                        dirHandle.Close();
                    }
                }
            }
            else
            {
                throw new InvalidOperationException(string.Format("Directory doesn't exist: {0}", DirectoryPath));
            }
        }

        private static void EnableCompression(SafeFileHandle handle)
        {
            int lpBytesReturned = 0;
            short lpInBuffer = COMPRESSION_FORMAT_DEFAULT;

            int result = DeviceIoControl(handle, FSCTL_SET_COMPRESSION,
                ref lpInBuffer, sizeof(short), IntPtr.Zero, 0,
                ref lpBytesReturned, IntPtr.Zero);

            if (result != 0)
            {
                Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
            }
        }

    }
}
