using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Yaulw.File;
using System.Xml.Serialization;
using System.Collections;
using Yaulw.Xml;
using System.Resources;
using System.Reflection;
using System.IO;
using Yaulw.Tools;
using Yaulw.Assembly;

namespace Installables.All
{      
    /// <summary>
    /// Responsible for extracting the Components that are embedded in this dll
    /// into the temporary directory for the installer to consume
    /// </summary>
    public class Component_Binary_Manager : IDisposable, IManageComponents
    {        
        #region Private Members
        
        private bool _disposed = false;

        private Dictionary<string, Assembly> _componentsLoaded = new Dictionary<string, Assembly>();        

        #endregion

        #region Construction

        /// <summary>
        /// Constructor
        /// </summary>
        public Component_Binary_Manager()
        {            
        }

        /// <summary>
        /// Finalizer
        /// </summary>
        ~Component_Binary_Manager()
        {
            Dispose(true);
        }

        #endregion

        #region IManageComponents Members

        /// <summary>
        /// Checks if there are Newer Components embedded then that were installed on the system
        /// </summary>
        /// <returns>true, if newer components were found, false otherwise</returns>
        public bool AreNewerComponentsAvailable()
        {
            //# If nothing is installed, no need to continue            
            if (GetAllInstalledComponents() == null)
                return true;                        

            // if the lengths don't match, something must get installed
            if (GetAllInstalledComponents().Length != GetAllEmbeddedComponents().Length)
                return true;

            // # Otherwise, let's determine 1 by 1
            foreach (ComponentConfig.Component component in GetAllEmbeddedComponents())
            {
                int nIndex = Common.GetIndexForComponentUniqueLabel(component.UniqueLabel, GetAllInstalledComponents());
                if (nIndex == -1)
                    return true;
                else if(GetAllInstalledComponents()[nIndex].CompareTo(component) != 0)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Returns the Component Definition containing the temporary file (extracted component)
        /// for all Newer components found on the system
        /// </summary>
        /// <param name="setupEventObj">Setup Event Object is passed, for component manager to pass down to it's components</param>
        /// <returns>a list of newer binary components, or empty list for none</returns>
        public IInstallComponent[] GetNewerComponents(ref SetupEvents setupEventObj)
        {                        
            // # Extract all, or let's determine 1 by 1 and extract
            bool bInstalledCompsFound = (GetAllInstalledComponents() != null);
            foreach (ComponentConfig.Component component in GetAllEmbeddedComponents())
            {
                bool bExtract = true;
                if (bInstalledCompsFound)
                {
                    int nIndex = Common.GetIndexForComponentUniqueLabel(component.UniqueLabel, GetAllInstalledComponents());
                    if (nIndex != -1)
                        bExtract = (GetAllInstalledComponents()[nIndex].CompareTo(component) != 0);
                }

                // mark component for Installation * Extract to File System *
                if (bExtract)
                {                                        
                    if (!ExtractComponentFromResourceToTempFileLocation(component))
                        Common.Log.Error(String.Format("Failed to Extract Component {0}", component.UniqueLabel));                    
                }              
            }
            
            List<IInstallComponent> ComponentsToInstall = new List<IInstallComponent>();
            if (_componentsLoaded.Count > 0)
            {
                foreach(Assembly asm in _componentsLoaded.Values)
                {                    
                    IInstallComponent installComp = null;
                    Type[] types = asm.GetTypes();
                    foreach (Type t in types)
                    {
                        if (t.GetInterface(typeof(IInstallComponent).Name) != null)
                        {
                            installComp = (IInstallComponent) asm.CreateInstance(t.FullName);
                            break;
                        }
                    }

                    // Check IInstallComponent was found
                    if (installComp == null)
                    {
                        Common.Log.Error(String.Format("Component {0} contains no IInstallComponent Definition.", AssemblyW.GetAssemblyName(asm)));
                        continue;
                    }
                    
                    // Check if class also implements ISetup
                    if(installComp is ISetup)
                        ((ISetup) installComp).ComponentLoaded(ref setupEventObj);
                    
                    // Add to Install Components
                    ComponentsToInstall.Add(installComp);                    
                }
            }
            return ComponentsToInstall.ToArray();
        }

        /// <summary>
        /// Retrieves the list of all installed components, in order to uninstall
        /// </summary>
        /// <returns>a list of all installed components, null otherwise</returns>
        public IInstallComponent[] GetAllInstalledComponents(string ComponentsSeperatedbySemiColonOrBlankForAll, ref SetupEvents setupEventObj)
        {
            // TO DO:
            return null;
        }

        /// <summary>
        /// Retrieves the list of all installed components
        /// </summary>
        /// <returns>a list of all installed components, null otherwise</returns>
        public ComponentConfig.Component[] GetAllInstalledComponents()
        {
            if (Common.InstalledConfig != null && Common.InstalledConfig.BinaryComponents.Components.Length > 0)
                return Common.InstalledConfig.BinaryComponents.Components;
            return null;
        }

        /// <summary>
        /// Retrieves the list of all installed componentsW
        /// </summary>
        /// <returns>a list of all installed componentsW, null otherwise</returns>
        public ComponentConfig.ComponentW GetAllInstalledComponentsW()
        {            
            return Common.InstalledConfig.BinaryComponents;         
        }

        /// <summary>
        /// Retrieves the list of all Embedded components
        /// </summary>
        /// <returns>a list of all embedded components, null otherwise</returns>
        public ComponentConfig.Component[] GetAllEmbeddedComponents()
        {
            if (Common.EmbeddedConfig != null && Common.EmbeddedConfig.BinaryComponents.Components.Length > 0)
                return Common.EmbeddedConfig.BinaryComponents.Components;
            return null;
        }

        #endregion

        #region Private Helpers

        /// <summary>
        /// Private Helper to physically extract the bits from the resource and write them to a temporary 
        /// file location
        /// </summary>
        /// <param name="component">Component, whose files are to be extracted</param>
        /// <returns>true, if successful, false otherwise</returns>
        private bool ExtractComponentFromResourceToTempFileLocation(ComponentConfig.Component component)
        {
            if (component != null)
            {                
                // Extract the component to the Temp Directory,
                // if it is not already there...
                for (int i = 0; i < component.FileNames.Length; ++i)
                {
                    // First try loading the assembly
                    string asmName = "Component.Binary." + component.UniqueLabel;
                    Assembly asm = Assembly.Load(asmName);
                    if (asm == null)
                        return false;
                    else
                        _componentsLoaded[component.UniqueLabel] = asm; // <- imp
                    string FileName = component.FileNames[i];
                    string TempFileNameNPath = component.TempFileNamesNPath[i];
                    if (!File.Exists(TempFileNameNPath))
                    {
                        using (BinaryReader br = new BinaryReader(asm.GetManifestResourceStream(asmName + "." + FileName)))
                        using (BinaryWriter bw = new BinaryWriter(new FileStream(TempFileNameNPath, FileMode.Create)))
                        {
                            byte[] buffer = new byte[64 * 1024];
                            int numread = br.Read(buffer, 0, buffer.Length);
                            while (numread > 0)
                            {
                                bw.Write(buffer, 0, numread);
                                numread = br.Read(buffer, 0, buffer.Length);
                            }
                            bw.Flush();
                        }
                    }
                }
                return true;
            }
            return false;
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Dispose of all extracted files
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            // Use SupressFinalize in case a subclass
            // of this type implements a finalizer
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Make sure to conserve disk space, to delete all files that were extracted
        /// by this program
        /// </summary>
        /// <param name="disposing">if true, delete all files extracted by this dll</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    // When Disposing, Delete all Temporary Files on the System that may exist                    
                    foreach (ComponentConfig.Component component in GetAllEmbeddedComponents())
                    {
                        foreach (string tFileNameNPath in component.TempFileNamesNPath)
                        {
                            if (File.Exists(tFileNameNPath))
                                File.Delete(tFileNameNPath);
                        }
                    }

                    _componentsLoaded.Clear();
                    _componentsLoaded = null;
                }

                // Indicate that the instance has been disposed.                                                
                _disposed = true;
            }
        }

        #endregion
    }
}
