using J2N;
using J2N.Threading;
using Lucene.Net.Attributes;
using Lucene.Net.Support;
using Lucene.Net.Util;
using NUnit.Framework;
using System;
using System.IO;
using System.Threading;
using Assert = Lucene.Net.TestFramework.Assert;
using Console = Lucene.Net.Util.SystemConsole;

namespace Lucene.Net.Store
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
    using TestUtil = Lucene.Net.Util.TestUtil;

    [TestFixture]
    public class TestDirectory : LuceneTestCase
    {
        [Test]
        public virtual void TestDetectClose()
        {
            DirectoryInfo tempDir = CreateTempDir(GetType().Name);
            Directory[] dirs = new Directory[] { new RAMDirectory(), new SimpleFSDirectory(tempDir), new NIOFSDirectory(tempDir) };

            foreach (Directory dir in dirs)
            {
                dir.Dispose();
                try
                {
                    dir.CreateOutput("test", NewIOContext(Random));
                    Assert.Fail("did not hit expected exception");
                }
#pragma warning disable 168
                catch (ObjectDisposedException ace)
#pragma warning restore 168
                {
                }
            }
        }

        // test is occasionally very slow, i dont know why
        // try this seed: 7D7E036AD12927F5:93333EF9E6DE44DE
        [Test]
        [Slow]
        //[Nightly] // This was marked Nightly in Java, but we are not seeing very bad performance, so leaving it in the normal flow
        public virtual void TestThreadSafety()
        {
            BaseDirectoryWrapper dir = NewDirectory();
            dir.CheckIndexOnDispose = false; // we arent making an index
            if (dir is MockDirectoryWrapper)
            {
                ((MockDirectoryWrapper)dir).Throttling = Throttling.NEVER; // makes this test really slow
            }

            if (Verbose)
            {
                Console.WriteLine(dir);
            }

            TheThread theThread = new TheThread("t1", dir);
            TheThread2 theThread2 = new TheThread2("t2", dir);
            theThread.Start();
            theThread2.Start();

            theThread.Join();
            theThread2.Join();

            dir.Dispose();
        }

        private class TheThread : ThreadJob
        {
            private readonly string name;
            private readonly BaseDirectoryWrapper outerBDWrapper;

            public TheThread(string name, BaseDirectoryWrapper baseDirectoryWrapper)
            {
                this.name = name;
                outerBDWrapper = baseDirectoryWrapper;
            }

            public override void Run()
            {
                for (int i = 0; i < 3000; ++i)
                {
                    string fileName = this.name + i;

                    try
                    {
                        using (IndexOutput output = outerBDWrapper.CreateOutput(fileName, NewIOContext(Random))) { }
                        Assert.IsTrue(SlowFileExists(outerBDWrapper, fileName));
                    }
                    catch (IOException e)
                    {
                        throw new Exception(e.ToString(), e);
                    }
                }
            }
        }

        private class TheThread2 : ThreadJob
        {
            private string name;
            private readonly BaseDirectoryWrapper outerBDWrapper;

            public TheThread2(string name, BaseDirectoryWrapper baseDirectoryWrapper)
            {
                this.name = name;
                outerBDWrapper = baseDirectoryWrapper;
            }

            public override void Run()
            {
                for (int i = 0; i < 10000; i++)
                {
                    try
                    {
                        string[] files = outerBDWrapper.ListAll();
                        foreach (string file in files)
                        {
                            try
                            {
                                using (IndexInput input = outerBDWrapper.OpenInput(file, NewIOContext(Random))) { }
                            }
#pragma warning disable 168
                            catch (FileNotFoundException fne)
#pragma warning restore 168
                            {
                            }
                            // LUCENENET specific - since NoSuchDirectoryException subclasses FileNotFoundException
                            // in Lucene, we need to catch it here to be on the safe side.
                            catch (DirectoryNotFoundException)
                            {
                            }
                            catch (IOException e)
                            {
                                if (!e.Message.Contains("still open for writing"))
                                {
                                    throw new Exception(e.ToString(), e);
                                }
                            }
                            if (Random.NextBoolean())
                            {
                                break;
                            }
                        }
                    }
                    catch (IOException e)
                    {
                        throw new Exception(e.ToString(), e);
                    }
                }
            }
        }

        // Test that different instances of FSDirectory can coexist on the same
        // path, can read, write, and lock files.
        [Test]
        public virtual void TestDirectInstantiation()
        {
            DirectoryInfo path = CreateTempDir("testDirectInstantiation");

            byte[] largeBuffer = new byte[Random.Next(256 * 1024)], largeReadBuffer = new byte[largeBuffer.Length];
            for (int i = 0; i < largeBuffer.Length; i++)
            {
                largeBuffer[i] = (byte)i; // automatically loops with modulo
            }

            var dirs = new FSDirectory[] { new SimpleFSDirectory(path, null), new NIOFSDirectory(path, null), new MMapDirectory(path, null) };

            for (int i = 0; i < dirs.Length; i++)
            {
                FSDirectory dir = dirs[i];
                dir.EnsureOpen();
                string fname = "foo." + i;
                string lockname = "foo" + i + ".lck";
                IndexOutput @out = dir.CreateOutput(fname, NewIOContext(Random));
                @out.WriteByte((byte)(sbyte)i);
                @out.WriteBytes(largeBuffer, largeBuffer.Length);
                @out.Dispose();

                for (int j = 0; j < dirs.Length; j++)
                {
                    FSDirectory d2 = dirs[j];
                    d2.EnsureOpen();
                    Assert.IsTrue(SlowFileExists(d2, fname));
                    Assert.AreEqual(1 + largeBuffer.Length, d2.FileLength(fname));

                    // LUCENENET specific - unmap hack not needed
                    //// don't do read tests if unmapping is not supported!
                    //if (d2 is MMapDirectory && !((MMapDirectory)d2).UseUnmap)
                    //{
                    //    continue;
                    //}

                    IndexInput input = d2.OpenInput(fname, NewIOContext(Random));
                    Assert.AreEqual((byte)i, input.ReadByte());
                    // read array with buffering enabled
                    Arrays.Fill(largeReadBuffer, (byte)0);
                    input.ReadBytes(largeReadBuffer, 0, largeReadBuffer.Length, true);
                    Assert.AreEqual(largeBuffer, largeReadBuffer);
                    // read again without using buffer
                    input.Seek(1L);
                    Arrays.Fill(largeReadBuffer, (byte)0);
                    input.ReadBytes(largeReadBuffer, 0, largeReadBuffer.Length, false);
                    Assert.AreEqual(largeBuffer, largeReadBuffer);
                    input.Dispose();
                }

                // delete with a different dir
                dirs[(i + 1) % dirs.Length].DeleteFile(fname);

                for (int j = 0; j < dirs.Length; j++)
                {
                    FSDirectory d2 = dirs[j];
                    Assert.IsFalse(SlowFileExists(d2, fname));
                }

                Lock @lock = dir.MakeLock(lockname);
                Assert.IsTrue(@lock.Obtain());

                for (int j = 0; j < dirs.Length; j++)
                {
                    FSDirectory d2 = dirs[j];
                    Lock lock2 = d2.MakeLock(lockname);
                    try
                    {
                        Assert.IsFalse(lock2.Obtain(1));
                    }
#pragma warning disable 168
                    catch (LockObtainFailedException e)
#pragma warning restore 168
                    {
                        // OK
                    }
                }

                @lock.Dispose();

                // now lock with different dir
                @lock = dirs[(i + 1) % dirs.Length].MakeLock(lockname);
                Assert.IsTrue(@lock.Obtain());
                @lock.Dispose();
            }

            for (int i = 0; i < dirs.Length; i++)
            {
                FSDirectory dir = dirs[i];
                dir.EnsureOpen();
                dir.Dispose();
                Assert.IsFalse(dir.IsOpen);
            }
        }

        // LUCENE-1464
        [Test]
        public virtual void TestDontCreate()
        {
            var parentFolder = CreateTempDir(this.GetType().Name.ToLowerInvariant());
            var path = new DirectoryInfo(Path.Combine(parentFolder.FullName, "doesnotexist"));
            try
            {
                Assert.IsTrue(!path.Exists);
                Directory dir = new SimpleFSDirectory(path, null);
                Assert.IsTrue(!path.Exists);
                dir.Dispose();
            }
            finally
            {
                if (path.Exists) System.IO.Directory.Delete(path.FullName, true);
            }
        }

        // LUCENE-1468
        [Test]
        public virtual void TestRAMDirectoryFilter()
        {
            CheckDirectoryFilter(new RAMDirectory());
        }

        // LUCENE-1468
        [Test]
        public virtual void TestFSDirectoryFilter()
        {
            CheckDirectoryFilter(NewFSDirectory(CreateTempDir("test")));
        }

        private static bool ContainsFile(Directory directory, string file) // LUCENENET specific method to prevent having to use Arrays.AsList(), which creates unnecessary memory allocations
        {
            return Array.IndexOf(directory.ListAll(), file) > -1;
        }

        // LUCENE-1468
        private void CheckDirectoryFilter(Directory dir)
        {
            string name = "file";
            try
            {
                dir.CreateOutput(name, NewIOContext(Random)).Dispose();
                Assert.IsTrue(SlowFileExists(dir, name));
                Assert.IsTrue(ContainsFile(dir, name));
            }
            finally
            {
                dir.Dispose();
            }
        }

        // LUCENE-1468
        [Test]
        public virtual void TestCopySubdir()
        {
            DirectoryInfo path = CreateTempDir("testsubdir");
            try
            {
                //path.mkdirs();
                System.IO.Directory.CreateDirectory(path.FullName);
                //(new File(path, "subdir")).mkdirs();
                System.IO.Directory.CreateDirectory(new DirectoryInfo(Path.Combine(path.FullName, "subdir")).FullName);
                Directory fsDir = new SimpleFSDirectory(path, null);
                Assert.AreEqual(0, (new RAMDirectory(fsDir, NewIOContext(Random))).ListAll().Length);
            }
            finally
            {
                System.IO.Directory.Delete(path.FullName, true);
            }
        }

        // LUCENE-1468
        [Test]
        public virtual void TestNotDirectory()
        {
            DirectoryInfo path = CreateTempDir("testnotdir");
            Directory fsDir = new SimpleFSDirectory(path, null);
            try
            {
                IndexOutput @out = fsDir.CreateOutput("afile", NewIOContext(Random));
                @out.Dispose();
                Assert.IsTrue(SlowFileExists(fsDir, "afile"));
                try
                {
                    var d = new SimpleFSDirectory(new DirectoryInfo(Path.Combine(path.FullName, "afile")), null);
                    Assert.Fail("did not hit expected exception");
                }
#pragma warning disable 168
                catch (DirectoryNotFoundException nsde)
#pragma warning restore 168
                {
                    // Expected
                }
            }
            finally
            {
                fsDir.Dispose();
                System.IO.Directory.Delete(path.FullName, true);
            }
        }

        [Test]
        public virtual void TestFsyncDoesntCreateNewFiles()
        {
            var path = CreateTempDir("nocreate");
            Console.WriteLine(path.FullName);

            using (Directory fsdir = new SimpleFSDirectory(path))
            {
                // write a file
                using (var o = fsdir.CreateOutput("afile", NewIOContext(Random)))
                {
                    o.WriteString("boo");
                }

                // delete it
                try
                {
                    File.Delete(Path.Combine(path.FullName, "afile"));
                }
                catch (Exception e)
                {
                    Assert.Fail("Deletion of new Directory should never fail.\nException thrown: {0}", e);
                }

                // directory is empty
                Assert.AreEqual(0, fsdir.ListAll().Length);


                // LUCENENET specific: Since FSDirectory.Sync() does not actually do anything in .NET
                // we decided to remove the exception as well. This is safe to ignore here.
                //// fsync it
                //try
                //{
                //    fsdir.Sync(Collections.Singleton("afile"));
                //    Assert.Fail("didn't get expected exception, instead fsync created new files: " +
                //                Collections.ToString(fsdir.ListAll()));
                //}
                //catch (FileNotFoundException)
                //{
                //    // ok
                //}
                //// LUCENENET specific - since NoSuchDirectoryException subclasses FileNotFoundException
                //// in Lucene, we need to catch it here to be on the safe side.
                //catch (DirectoryNotFoundException)
                //{
                //    // ok
                //}

                // directory is still empty
                Assert.AreEqual(0, fsdir.ListAll().Length);
            }
        }

        [Test]
        [Slow]
        [LuceneNetSpecific]
        public virtual void ConcurrentIndexAccessThrowsWithoutSynchronizedStaleFiles()
        {
            DirectoryInfo tempDir = CreateTempDir(GetType().Name);
            using (Directory dir = new SimpleFSDirectory(tempDir))
            {
                var ioContext = NewIOContext(Random);
                var threads = new Thread[Environment.ProcessorCount];
                int file = 0;
                Exception exception = null;
                bool stopped = false;

                using (var @event = new ManualResetEvent(false))
                {
                    for (int i = 0; i < threads.Length; i++)
                    {
                        var thread = new Thread(() =>
                        {
                            while (!stopped)
                            {
                                int nextFile = Interlocked.Increment(ref file);
                                try
                                {
                                    dir.CreateOutput("test" + nextFile, ioContext).Dispose();
                                }
                                catch (Exception ex)
                                {
                                    exception = ex;
                                    @event.Set();
                                    break;
                                }
                            }
                        });
                        thread.Start();
                        threads[i] = thread;
                    }

                    bool raised = @event.WaitOne(TimeSpan.FromSeconds(5));

                    stopped = true;

                    if (raised)
                        throw new Exception("Test failed", exception);
                }

                foreach (var thread in threads)
                {
                    thread.Join();
                }
            }
        }


#pragma warning disable 612, 618
        [Test]
        [LuceneNetSpecific]
        public void TestLUCENENET521()
        {
            var newDirectoryInfo = CreateTempDir("LUCENENET521");
            using (var zipFileStream = this.GetType().FindAndGetManifestResourceStream("LUCENENET521.zip"))
            {
                TestUtil.Unzip(zipFileStream, newDirectoryInfo);
            }

            var newDirectory = new MMapDirectory(newDirectoryInfo);
            var conf = new Index.IndexWriterConfig(LuceneVersion.LUCENE_30, new Analysis.Standard.StandardAnalyzer(LuceneVersion.LUCENE_30));
            var indexWriter = new Index.IndexWriter(newDirectory, conf);
            indexWriter.Dispose();

            var sharedReader = Index.IndexReader.Open(newDirectory /*, true*/);
            const int times = 10;
            const int concurrentTaskCount = 10;
            var task = new System.Threading.Tasks.Task[concurrentTaskCount];
            for (int i = 0; i < concurrentTaskCount; i++)
            {
                task[i] = new System.Threading.Tasks.Task(() => Search(sharedReader, times));
                task[i].Start();
            }

            System.Threading.Tasks.Task.WaitAll(task);
            return;
        }
#pragma warning restore 612, 618

        private static void Search(Index.IndexReader r, int times)
        {
            var searcher = new Search.IndexSearcher(r);
            var docs = new System.Collections.Generic.List<Documents.Document>(10000);
            for (int i = 0; i < times; i++)
            {
                var q = new Search.TermQuery(new Index.Term("title", "volume"));
                foreach (var scoreDoc in searcher.Search(q, 100).ScoreDocs)
                {
                    docs.Add(searcher.Doc(scoreDoc.Doc));
                }
            }
        }
    }
}