using Microsoft.Win32;
using Pilz.Win32.Mapped;
using Pilz.Win32.Native;
using System.Reflection;
using System.Runtime.InteropServices;
using static Pilz.Win32.Native.Shell32;

namespace Pilz.Win32.Internals;

public class IconFactory
{

    #region Custom exceptions class

    public class IconNotFoundException : Exception
    {
        public IconNotFoundException(string fileName, int index) : base(string.Format("Icon with Id = {0} wasn't found in file {1}", index, fileName))
        {
        }
    }

    public class UnableToExtractIconsException : Exception
    {
        public UnableToExtractIconsException(string fileName, int firstIconIndex, int iconCount) : base(string.Format("Tryed to extract {2} icons starting from the one with id {1} from the \"{0}\" file but failed", fileName, firstIconIndex, iconCount))
        {
        }
    }

    #endregion


    /// <summary>
    /// Get the number of icons in the specified file.
    /// </summary>
    /// <param name="fileName">Full path of the file to look for.</param>
    /// <returns></returns>
    private static int GetIconsCountInFile(string fileName)
    {
        return ExtractIconEx(fileName, -1, null, null, 0);
    }

    #region ExtractIcon-like functions

    public static void ExtractEx(string fileName, List<Icon> largeIcons, List<Icon> smallIcons, int firstIconIndex, int iconCount)
    {
        // 
        // Memory allocations
        // 

        var smallIconsPtrs = (nint[])null;
        var largeIconsPtrs = (nint[])null;

        if (smallIcons is not null)
            smallIconsPtrs = new nint[iconCount];
        if (largeIcons is not null)
            largeIconsPtrs = new nint[iconCount];

        // 
        // Call to native Win32 API
        // 

        var apiResult = ExtractIconEx(fileName, firstIconIndex, largeIconsPtrs, smallIconsPtrs, iconCount);
        if (apiResult != iconCount)
            throw new UnableToExtractIconsException(fileName, firstIconIndex, iconCount);

        // 
        // Fill lists
        // 

        if (smallIcons is not null)
        {
            smallIcons.Clear();
            foreach (var actualIconPtr in smallIconsPtrs)
                smallIcons.Add(Icon.FromHandle(actualIconPtr));
        }
        if (largeIcons is not null)
        {
            largeIcons.Clear();
            foreach (var actualIconPtr in largeIconsPtrs)
                largeIcons.Add(Icon.FromHandle(actualIconPtr));
        }
    }

    public static List<Icon> ExtractEx(string fileName, SystemIconSize size, int firstIconIndex, int iconCount)
    {
        var iconList = new List<Icon>();

        switch (size)
        {
            case SystemIconSize.Large:
                {
                    ExtractEx(fileName, iconList, null, firstIconIndex, iconCount);
                    break;
                }
            case SystemIconSize.Small:
                {
                    ExtractEx(fileName, null, iconList, firstIconIndex, iconCount);
                    break;
                }

            default:
                {
                    throw new ArgumentOutOfRangeException("size");
                }
        }

        return iconList;
    }

    public static void Extract(string fileName, List<Icon> largeIcons, List<Icon> smallIcons)
    {
        var iconCount = GetIconsCountInFile(fileName);
        ExtractEx(fileName, largeIcons, smallIcons, 0, iconCount);
    }

    public static List<Icon> Extract(string fileName, SystemIconSize size)
    {
        var iconCount = GetIconsCountInFile(fileName);
        return ExtractEx(fileName, size, 0, iconCount);
    }

    public static Icon ExtractOne(string fileName, int index, SystemIconSize size)
    {
        try
        {
            var iconList = ExtractEx(fileName, size, index, 1);
            return iconList[0];
        }
        catch (UnableToExtractIconsException __unusedUnableToExtractIconsException1__)
        {
            throw new IconNotFoundException(fileName, index);
        }
    }

    public static void ExtractOne(string fileName, int index, out Icon largeIcon, out Icon smallIcon)
    {
        var smallIconList = new List<Icon>();
        var largeIconList = new List<Icon>();
        try
        {
            ExtractEx(fileName, largeIconList, smallIconList, index, 1);
            largeIcon = largeIconList[0];
            smallIcon = smallIconList[0];
        }
        catch (UnableToExtractIconsException __unusedUnableToExtractIconsException1__)
        {
            throw new IconNotFoundException(fileName, index);
        }
    }

    #endregion

    // this will look throw the registry 
    // to find if the Extension have an icon.
    public static Icon IconFromExtension(string extension, SystemIconSize size)
    {
        // Add the '.' to the extension if needed
        if (extension[0] != '.')
            extension = '.' + extension;

        // opens the registry for the wanted key.
        var Root = Registry.ClassesRoot;
        var ExtensionKey = Root.OpenSubKey(extension);
        ExtensionKey.GetValueNames();
        var ApplicationKey = Root.OpenSubKey(ExtensionKey.GetValue("").ToString());

        // gets the name of the file that have the icon.
        var IconLocation = ApplicationKey.OpenSubKey("DefaultIcon").GetValue("").ToString();
        var IconPath = IconLocation.Split(',');

        if (Equals(IconPath[1], null))
            IconPath[1] = "0";
        var Large = new nint[1];
        var Small = new nint[1];

        // extracts the icon from the file.
        ExtractIconEx(IconPath[0], Convert.ToInt16(IconPath[1]), Large, Small, 1);
        return size == SystemIconSize.Large ? Icon.FromHandle(Large[0]) : Icon.FromHandle(Small[0]);
    }

    public static Icon IconFromExtensionShell(string extension, SystemIconSize size)
    {
        // add '.' if nessesry
        if (extension[0] != '.')
            extension = '.' + extension;

        // temp struct for getting file shell info
        var fileInfo = new SHFILEINFO();

        Shell32.SHGetFileInfo(extension, 0U, ref fileInfo, (uint)Marshal.SizeOf(fileInfo), (uint)(FileInfoFlags.SHGFI_ICON | FileInfoFlags.SHGFI_USEFILEATTRIBUTES | (FileInfoFlags)size));

        return Icon.FromHandle(fileInfo.hIcon);
    }

    public static Icon IconFromResource(string resourceName)
    {
        var @assembly = Assembly.GetCallingAssembly();

        return new Icon(assembly.GetManifestResourceStream(resourceName));
    }

    /// <summary>
    /// Parse strings in registry who contains the name of the icon and
    /// the index of the icon an return both parts.
    /// </summary>
    /// <param name="regString">The full string in the form "path,index" as found in registry.</param>
    /// <param name="fileName">The "path" part of the string.</param>
    /// <param name="index">The "index" part of the string.</param>
    public static void ExtractInformationsFromRegistryString(string regString, out string fileName, out int index)
    {
        if (Equals(regString, null))
            throw new ArgumentNullException("regString");
        if (regString.Length == 0)
            throw new ArgumentException("The string should not be empty.", "regString");

        index = 0;
        var strArr = regString.Replace("\"", "").Split(',');
        fileName = strArr[0].Trim();
        if (strArr.Length > 1)
            int.TryParse(strArr[1].Trim(), out index);
    }

    public static Icon ExtractFromRegistryString(string regString, SystemIconSize size)
    {
        string fileName;
        int index;
        ExtractInformationsFromRegistryString(regString, out fileName, out index);
        return ExtractOne(fileName, index, size);
    }

}