using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
namespace EPocalipse.IFilter
{
  /// 
  /// FilterLoader finds the dll and ClassID of the COM object responsible  
  /// for filtering a specific file extension. 
  /// It then loads that dll, creates the appropriate COM object and returns 
  /// a pointer to an IFilter instance
  /// 
  static class FilterLoader
  {
    #region CacheEntry
    private class CacheEntry
    {
      public string DllName;
      public string ClassName;
      public CacheEntry(string dllName, string className)
      {
        DllName=dllName;
        ClassName=className;
      }
    }
    #endregion
    static Dictionary _cache=new Dictionary();
    #region Registry Read String helper
    static string ReadStrFromHKLM(string key)
    {
      return ReadStrFromHKLM(key,null);
    }
    static string ReadStrFromHKLM(string key, string value)
    {
      RegistryKey rk=Registry.LocalMachine.OpenSubKey(key);
      if (rk==null)
        return null;
      using (rk)
      {
        return (string)rk.GetValue(value);
      }
    }
    #endregion
    /// 
    /// finds an IFilter implementation for a file type
    /// 
    /// The extension of the file
    /// an IFilter instance used to retreive text from that file type
    private static IFilter LoadIFilter(string ext)
    {
      string dllName, filterPersistClass;
      //Find the dll and ClassID
      if (GetFilterDllAndClass(ext, out dllName, out filterPersistClass))
      {
        //load the dll and return an IFilter instance.
        return LoadFilterFromDll(dllName, filterPersistClass);
      }
      return null;
    }
    internal static IFilter LoadAndInitIFilter(string fileName)
    {
      return LoadAndInitIFilter(fileName,Path.GetExtension(fileName));
    }
    internal static IFilter LoadAndInitIFilter(string fileName, string extension)
    {
      IFilter filter=LoadIFilter(extension);
      if (filter==null)
        return null;
      IPersistFile persistFile=(filter as IPersistFile);
      if (persistFile!=null)
      {
        persistFile.Load(fileName, 0);
        IFILTER_FLAGS flags;
        IFILTER_INIT iflags =
					IFILTER_INIT.CANON_HYPHENS |
					IFILTER_INIT.CANON_PARAGRAPHS |
					IFILTER_INIT.CANON_SPACES |
					IFILTER_INIT.APPLY_INDEX_ATTRIBUTES |
					IFILTER_INIT.HARD_LINE_BREAKS |
					IFILTER_INIT.FILTER_OWNED_VALUE_OK;
        if (filter.Init(iflags, 0, IntPtr.Zero, out flags)==IFilterReturnCode.S_OK)
          return filter;
      }
      //If we failed to retreive an IPersistFile interface or to initialize 
      //the filter, we release it and return null.
      Marshal.ReleaseComObject(filter);
      return null;
    }
    private static IFilter LoadFilterFromDll(string dllName, string filterPersistClass)
    {
      //Get a classFactory for our classID
      IClassFactory classFactory=ComHelper.GetClassFactory(dllName, filterPersistClass);
      if (classFactory==null)
        return null;
      //And create an IFilter instance using that class factory
      Guid IFilterGUID=new Guid("89BCB740-6119-101A-BCB7-00DD010655AF");
      Object obj;
      classFactory.CreateInstance(null, ref IFilterGUID, out obj);
      return (obj as IFilter);
    }
    private static bool GetFilterDllAndClass(string ext, out string dllName, out string filterPersistClass)
    {
      if (!GetFilterDllAndClassFromCache(ext, out dllName, out filterPersistClass))
      {
        string persistentHandlerClass;
        persistentHandlerClass=GetPersistentHandlerClass(ext,true);
        if (persistentHandlerClass!=null)
        {
          GetFilterDllAndClassFromPersistentHandler(persistentHandlerClass,
            out dllName, out filterPersistClass);
        }
        AddExtensionToCache(ext, dllName, filterPersistClass);
      }
      return (dllName!=null && filterPersistClass!=null); 
    }
    private static void AddExtensionToCache(string ext, string dllName, string filterPersistClass)
    {
      lock (_cache)
      {
        _cache.Add(ext.ToLower(), new CacheEntry(dllName, filterPersistClass));
      }
    }
    private static bool GetFilterDllAndClassFromPersistentHandler(string persistentHandlerClass, out string dllName, out string filterPersistClass)
    {
      dllName=null;
      filterPersistClass=null;
      //Read the CLASS ID of the IFilter persistent handler
      filterPersistClass=ReadStrFromHKLM(@"Software\Classes\CLSID\" + persistentHandlerClass + 
        @"\PersistentAddinsRegistered\{89BCB740-6119-101A-BCB7-00DD010655AF}");
      if (String.IsNullOrEmpty(filterPersistClass))
          return false;
      //Read the dll name 
      dllName=ReadStrFromHKLM(@"Software\Classes\CLSID\" + filterPersistClass + 
        @"\InprocServer32");
      return (!String.IsNullOrEmpty(dllName));
    }
    private static string GetPersistentHandlerClass(string ext, bool searchContentType)
    {
      //Try getting the info from the file extension
      string persistentHandlerClass=GetPersistentHandlerClassFromExtension(ext);
      if (String.IsNullOrEmpty(persistentHandlerClass))
        //try getting the info from the document type 
        persistentHandlerClass=GetPersistentHandlerClassFromDocumentType(ext);
      if (searchContentType && String.IsNullOrEmpty(persistentHandlerClass))
        //Try getting the info from the Content Type
        persistentHandlerClass=GetPersistentHandlerClassFromContentType(ext);
      return persistentHandlerClass;
    }
    private static string GetPersistentHandlerClassFromContentType(string ext)
    {
      string contentType=ReadStrFromHKLM(@"Software\Classes\"+ext,"Content Type");
      if (String.IsNullOrEmpty(contentType))
        return null;
      
      string contentTypeExtension=ReadStrFromHKLM(@"Software\Classes\MIME\Database\Content Type\"+contentType,
          "Extension");
      if (ext.Equals(contentTypeExtension, StringComparison.CurrentCultureIgnoreCase))
        return null; //No need to look further. This extension does not have any persistent handler
    
      //We know the extension that is assciated with that content type. Simply try again with the new extension
      return GetPersistentHandlerClass(contentTypeExtension, false); //Don't search content type this time.
    }
    private static string GetPersistentHandlerClassFromDocumentType(string ext)
    {
      //Get the DocumentType of this file extension
      string docType=ReadStrFromHKLM(@"Software\Classes\"+ext);
      if (String.IsNullOrEmpty(docType))
        return null;
      
      //Get the Class ID for this document type
      string docClass=ReadStrFromHKLM(@"Software\Classes\" + docType + @"\CLSID");
      if (String.IsNullOrEmpty(docType))
        return null;
      //Now get the PersistentHandler for that Class ID
      return ReadStrFromHKLM(@"Software\Classes\CLSID\" + docClass + @"\PersistentHandler");
    }
    private static string GetPersistentHandlerClassFromExtension(string ext)
    {
      return ReadStrFromHKLM(@"Software\Classes\"+ext+@"\PersistentHandler");
    }
    private static bool GetFilterDllAndClassFromCache(string ext, out string dllName, out string filterPersistClass)
    {
      string lowerExt=ext.ToLower();
      lock (_cache)
      {
        CacheEntry cacheEntry;
        if (_cache.TryGetValue(lowerExt, out cacheEntry))
        {
          dllName=cacheEntry.DllName;
          filterPersistClass=cacheEntry.ClassName;
          return true;
        }
      }
      dllName=null;
      filterPersistClass=null;
      return false;
    }
  }
}