using System;
using System.IO;
using System.Linq;
using System.Security;
using System.Web;
using System.Web.Compilation;
using Examine;
using Examine.LuceneEngine.Config;
using Examine.Providers;
using Examine.SearchCriteria;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Umbraco.Core;
using UmbracoExamine.Config;
using Examine.LuceneEngine;
using Examine.LuceneEngine.Providers;
using Examine.LuceneEngine.SearchCriteria;
using Lucene.Net.Analysis;
using Umbraco.Core.Logging;
using UmbracoExamine.LocalStorage;


namespace UmbracoExamine
{
    /// <summary>
    /// An Examine searcher which uses Lucene.Net as the 
    /// </summary>
    public class UmbracoExamineSearcher : LuceneSearcher
    {

        private volatile Lucene.Net.Store.Directory _localTempDirectory;
        private static readonly object Locker = new object();
        private string _localTempPath = null;
        private LocalStorageType _localStorageType = LocalStorageType.Sync;

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public UmbracoExamineSearcher()
            : base()
        {
        }

        private string _name;

        /// <summary>
        /// we override name because we need to manually set it if !CanInitialize() 
        /// since we cannot call base.Initialize in that case.
        /// </summary>
        public override string Name
        {
            get { return _name; }
        }
        
        /// <summary>
        /// Constructor to allow for creating an indexer at runtime
        /// </summary>
        /// <param name="indexPath"></param>
        /// <param name="analyzer"></param>

        public UmbracoExamineSearcher(DirectoryInfo indexPath, Analyzer analyzer)
            : base(indexPath, analyzer)
        {
        }

        /// <summary>
        /// Constructor to allow for creating an indexer at runtime
        /// </summary>
        /// <param name="luceneDirectory"></param>
        /// <param name="analyzer"></param>
        public UmbracoExamineSearcher(Lucene.Net.Store.Directory luceneDirectory, Analyzer analyzer)
            : base(luceneDirectory, analyzer)
        {
        }

        /// <summary>
        /// Creates an NRT searcher
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="analyzer"></param>
        public UmbracoExamineSearcher(IndexWriter writer, Analyzer analyzer) 
            : base(writer, analyzer)
        {
        }

        #endregion

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (name == null) throw new ArgumentNullException("name");

            //ensure name is set
            _name = name;

            //We need to check if we actually can initialize, if not then don't continue
            if (CanInitialize() == false)
            {
                return;
            }

            base.Initialize(name, config);

            //NOTES: useTempStorage is obsolete, tempStorageDirectory is obsolete, both have been superceded by Examine Core's IDirectoryFactory
            //       tempStorageDirectory never actually got finished in Umbraco Core but accidentally got shipped (it's only enabled on the searcher
            //       and not the indexer). So this whole block is just legacy

            //detect if a dir factory has been specified, if so then useTempStorage will not be used (deprecated)
            if (config != null && config["directoryFactory"] == null && config["useTempStorage"] != null)
            {
                //Use the temp storage directory which will store the index in the local/codegen folder, this is useful
                // for websites that are running from a remove file server and file IO latency becomes an issue
                var attemptUseTempStorage = config["useTempStorage"].TryConvertTo<LocalStorageType>();
                if (attemptUseTempStorage)
                {
                    //this is the default
                    ILocalStorageDirectory localStorageDir = new CodeGenLocalStorageDirectory();
                    if (config["tempStorageDirectory"] != null)
                    {
                        //try to get the type
                        var dirType = BuildManager.GetType(config["tempStorageDirectory"], false);
                        if (dirType != null)
                        {
                            try
                            {
                                localStorageDir = (ILocalStorageDirectory)Activator.CreateInstance(dirType);
                            }
                            catch (Exception ex)
                            {
                                LogHelper.Error<UmbracoExamineSearcher>(
                                    string.Format("Could not create a temp storage location of type {0}, reverting to use the " + typeof(CodeGenLocalStorageDirectory).FullName, dirType),
                                    ex);
                            }
                        }
                    }
                    var indexSet = IndexSets.Instance.Sets[IndexSetName];
                    var configuredPath = indexSet.IndexPath;
                    var tempPath = localStorageDir.GetLocalStorageDirectory(config, configuredPath);
                    if (tempPath == null) throw new InvalidOperationException("Could not resolve a temp location from the " + localStorageDir.GetType() + " specified");
                    _localTempPath = tempPath.FullName;
                    _localStorageType = attemptUseTempStorage.Result;
                }
            }
        }

        /// <summary>
        /// Used for unit tests
        /// </summary>
        internal static bool? DisableInitializationCheck = null;

        /// <summary>
        /// Returns true if the Umbraco application is in a state that we can initialize the examine indexes
        /// </summary>
        /// <returns></returns>

        protected bool CanInitialize()
        {
            //check the DisableInitializationCheck and ensure that it is not set to true
            if (!DisableInitializationCheck.HasValue || !DisableInitializationCheck.Value)
            {
                //We need to check if we actually can initialize, if not then don't continue
                if (ApplicationContext.Current == null
                    || !ApplicationContext.Current.IsConfigured
                    || !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Override in order to set the nodeTypeAlias field name of the underlying SearchCriteria to __NodeTypeAlias
        /// </summary>
        /// <param name="type"></param>
        /// <param name="defaultOperation"></param>
        /// <returns></returns>
        public override ISearchCriteria CreateSearchCriteria(string type, BooleanOperation defaultOperation)
        {
            var criteria = base.CreateSearchCriteria(type, defaultOperation) as LuceneSearchCriteria;
            criteria.NodeTypeAliasField = UmbracoContentIndexer.NodeTypeAliasFieldName;
            return criteria;
        }

        /// <summary>
        /// Returns a list of fields to search on, this will also exclude the IndexPathFieldName and node type alias
        /// </summary>
        /// <returns></returns>
        protected internal override string[] GetSearchFields()
        {
            var fields = base.GetSearchFields();
            return fields
                .Where(x => x != UmbracoContentIndexer.IndexPathFieldName)
                .Where(x => x != UmbracoContentIndexer.NodeTypeAliasFieldName)
                .ToArray();
        }

        protected override IndexReader OpenNewReader()
        {
            var directory = GetLuceneDirectory();
            return IndexReader.Open(
                directory, 
                //DeletePolicyTracker.Current.GetPolicy(directory), 
                true);
        }

        protected override Lucene.Net.Store.Directory GetLuceneDirectory()
        {
            //local temp storage is not enabled, just return the default
            if (_localTempPath == null) return base.GetLuceneDirectory();

            //local temp storage is enabled, configure the local directory instance
            if (_localTempDirectory == null)
            {
                lock (Locker)
                {
                    if (_localTempDirectory == null)
                    {
                        switch (_localStorageType)
                        {
                            case LocalStorageType.Sync:
                                var fsDir = base.GetLuceneDirectory() as FSDirectory;
                                if (fsDir != null)
                                {
                                    _localTempDirectory = LocalTempStorageDirectoryTracker.Current.GetDirectory(
                                        new DirectoryInfo(_localTempPath),
                                        fsDir);
                                }
                                else
                                {
                                    return base.GetLuceneDirectory();
                                }
                                break;
                            case LocalStorageType.LocalOnly:
                                _localTempDirectory = DirectoryTracker.Current.GetDirectory(new DirectoryInfo(_localTempPath));
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }


                    }
                }
            }

            return _localTempDirectory;
        }
    }
}
