#region License
/*
* Copyright 2002-2004 the original author or authors.
* 
* Licensed 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.
*/
#endregion
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using log4net;
using NUnit.Framework;
using Spring.Core.IO;
using Spring.Objects.Factory.Xml;
using Spring.Threading;

namespace Spring.Services.WindowsService.Common.Deploy.FileSystem
{
    public class TestingDispatcher : IDeployEventDispatcher
    {
        ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        IList events = new ArrayList();
        ISync wait = new Semaphore(0);

        public void PerformDispatch (object sender)
        {
            lock (this)
            {
                log.Debug(String.Format("performing dispatch: {0} events", events.Count));
                foreach (DeployEventArgs eventArgs in events)
                {
                    if (DeployEvent != null)
                    {
                        log.Debug(String.Format("raising event {0}", eventArgs.EventType));
                        DeployEvent(sender, eventArgs);
                    }
                }
                log.Debug(String.Format("clearing events list", events.Count));
                events.Clear();
            }
        }

        public void Dispatch (IDeployLocation sender, DeployEventType eventType, IApplication application)
        {
            lock (this)
            {
                log.Debug(String.Format("collecting event {0} (collected so far: {1})", eventType, events.Count));
                events.Add(new DeployEventArgs(application, DeployEventType.ApplicationUpdated));
                wait.Release();
            }
        }

        public event DeployEventHandler DeployEvent;

        public void Dispose ()
        {
        }

        public void Wait()
        {
            wait.Acquire();
        }

        public void WaitAndPerformDispatch(object o)
        {
            Wait();
            PerformDispatch(o);
        }
    }


    // problems with dynamic mocks: too many calls 
    public class MockFileSystemEventFilter : IFileSystemEventFilter
    {
        public bool called = false;
        public bool accept;

        public MockFileSystemEventFilter (bool accept)
        {
            this.accept = accept;
        }

        public bool Filter (FileSystemEventArgs args)
        {
            called = true;
            return accept;
        }
    }


    [TestFixture]
    public class FilteringSupportTest
    {
        IList allow;
        IList disallow;
        FileSystemEventArgs anEvent;
        FilteringSupport support;
        FileSystemEventArgs springAssemblyEvent;
        IApplication application;

        [SetUp]
        public void SetUp ()
        {
            allow = new ArrayList();
            disallow = new ArrayList();
            application = new Application(Path.GetFullPath("foo"));
            anEvent = new FileSystemEventArgs(WatcherChangeTypes.All, "foo", "foo.dll");
            springAssemblyEvent = 
                new FileSystemEventArgs(WatcherChangeTypes.All, 
                    Path.GetFullPath(SpringAssembliesDeployer.PrivateBinPathPrefix + "foo"), "foo.dll");
            support = new FilteringSupport(allow, disallow, SpringAssembliesDeployer.DisallowFilter);
        }

        [Test]
        public void AnEventIsAcceptedIfThereIsNoFilterDefined ()
        {
            Assert.IsTrue(support.Accept(anEvent), "event not accepted with no filter defined");
        }

        [Test]
        public void EventsRelatedToSpringAssembliesAreNeverAccepted ()
        {
            Assert.IsFalse(support.Accept(springAssemblyEvent), "spring assemblies event accepted");
            allow.Add(new RegularExpressionFilter(
                String.Format ("**/{0}*/**", SpringAssembliesDeployer.PrivateBinPathPrefix), true));
            Assert.IsFalse(support.Accept(springAssemblyEvent), "spring assemblies event accepted");
        }

        [Test]
        public void AnEventIsAcceptedIfAtLeastOneAllowFilterFiltersIt ()
        {

            allow.Add(new MockFileSystemEventFilter(false));            
            Assert.IsFalse(support.Accept(anEvent), "allowed when no allow filter allows");
            allow.Add(new MockFileSystemEventFilter(true));            
            Assert.IsTrue(support.Accept(anEvent), "not allowed when one allow filter allows");
            allow.Add(new MockFileSystemEventFilter(false));
            Assert.IsTrue(support.Accept(anEvent), "not allowed when one allow filter allows");
        }

        [Test]
        public void AnEventIsNotAcceptedIfAtLeastOneDisallowFilterFiltersIt ()
        {
            disallow.Add(new MockFileSystemEventFilter(false));            
            Assert.IsTrue(support.Accept(anEvent), "disallowed when no disallow filter disallows");
            disallow.Add(new MockFileSystemEventFilter(true));            
            Assert.IsFalse(support.Accept(anEvent), "allowed when one disallow filter disallows");
            disallow.Add(new MockFileSystemEventFilter(false));
            Assert.IsFalse(support.Accept(anEvent), "allowed when one disallow filter disallows");
        }

        [Test]
        public void ADisallowingFilterDominatesAllowFilters ()
        {
            disallow.Add(new MockFileSystemEventFilter(true));            
            allow.Add(new MockFileSystemEventFilter(false));            
            Assert.IsFalse(support.Accept(anEvent), "event accepted when a disallow filter disallows");
            allow.Add(new MockFileSystemEventFilter(true));            
            Assert.IsFalse(support.Accept(anEvent), "event accepted when a disallow filter disallows");
        }

        [Test]
        public void WithNoDisallowingFilterItDependsOnThePresenceOfAnAllowingFilters ()
        {
            disallow.Add(new MockFileSystemEventFilter(false));            
            allow.Add(new MockFileSystemEventFilter(false));            
            Assert.IsFalse(support.Accept(anEvent), "event accepted when a disallow filter disallows");
            allow.Add(new MockFileSystemEventFilter(true));            
            Assert.IsTrue(support.Accept(anEvent), "event not accepted when an allow filter allows and no disallow filter disallows");
            disallow.Add(new MockFileSystemEventFilter(true));            
            Assert.IsFalse(support.Accept(anEvent), "event accepted when a disallow filter disallows");
        }

        [Test]
        public void IgnoreEventsRegardingTheDirectoryOfTheApplication()
        {
            FileSystemEventArgs e = 
                new FileSystemEventArgs(WatcherChangeTypes.All, 
                Directory.GetCurrentDirectory(), "foo");
            Assert.IsFalse(support.IsApplicationEvent(e, application, true));

            e = new FileSystemEventArgs(WatcherChangeTypes.All, 
                Path.GetFullPath("foo"), "foo.dll");
            Assert.IsTrue(support.IsApplicationEvent(e, application, true));

            e = new FileSystemEventArgs(WatcherChangeTypes.All, 
                Directory.GetCurrentDirectory(), "foo/foo.dll");
            Assert.IsTrue(support.IsApplicationEvent(e, application, true));

            e = new FileSystemApplicationWatcher(application, 
                new NullWatcherConfigurer()).GenericApplicationEvent;
            Assert.IsTrue(support.IsApplicationEvent(e, application, true));
        }

        [Test]
        public void CanDecideIfAnEventIsAnApplicationEventIgnoringCase()
        {
            FileSystemEventArgs e = 
                new FileSystemEventArgs(WatcherChangeTypes.All, 
                Directory.GetCurrentDirectory(), "FOO/FOO.DLL");
            application = new Application(Path.GetFullPath("foo").ToUpper());
            Assert.IsTrue(support.IsApplicationEvent(e, application, true), "failed to match without case");
            e = new FileSystemEventArgs(WatcherChangeTypes.All, "C:/", "foo/foo.dll");
            Assert.IsFalse(support.IsApplicationEvent(e, application, false), "unexpected match with case");
        }

    }
	

    [TestFixture]
	public class FileSystemApplicationWatcherTest
	{
        ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        Latch eventLatch;
        string appName, anotherAppName;
        string appFullPath, anotherAppFullPath;
        private IDeployEventDispatcher dispatcher;
        private FileSystemApplicationWatcher watcher;
        private IApplication application;
        private bool dispatched;
        private ISync controlledEventLatch;


        [SetUp]
        public void SetUp ()
        {
            TestUtils.ConfigureLog4Net();
            dispatched = false;
            appName = Guid.NewGuid().ToString();
            appFullPath = Path.GetFullPath(appName);
            anotherAppName = Guid.NewGuid().ToString();
            anotherAppFullPath = Path.GetFullPath(anotherAppName);
            Directory.CreateDirectory(appFullPath);
            Directory.CreateDirectory(anotherAppFullPath);
            eventLatch = new Latch();
            controlledEventLatch = new Latch();
            dispatcher = new ForwardingDeployEventDispatcher();
            application = new Application(appFullPath);
            dispatcher.DeployEvent += new DeployEventHandler(dispatcher_DeployEvent);
            watcher = new FileSystemApplicationWatcher(application);
        }

        [TearDown]
        public void TearDown ()
        {
            if (watcher != null)
            {
                watcher.StopWatching();
                watcher.Dispose();
            }
            dispatcher.DeployEvent -= new DeployEventHandler(dispatcher_DeployEvent);
            Directory.Delete(appFullPath, true);
            Directory.Delete(anotherAppFullPath, true);
        }

        [Test]
		public void ProduceUpdateEventsForWatchedApplication()
		{
            TestingDispatcher controlledDispatcher = new TestingDispatcher();
            controlledDispatcher.DeployEvent += new DeployEventHandler(controlledDispatcher_DeployEvent);
            watcher.StartWatching(controlledDispatcher);
            string subDir = Path.Combine(appFullPath, "foo");
            Directory.CreateDirectory(subDir);
            controlledDispatcher.WaitAndPerformDispatch(null);

            using (File.Create(Path.Combine (subDir, "foo.bar"))) {}
            controlledDispatcher.WaitAndPerformDispatch(null);
        }

        [Test]
		public void DoNotProduceUpdateEventsForOtherApplications()
		{
            TestingDispatcher controlledDispatcher = new TestingDispatcher();
            controlledDispatcher.DeployEvent += new DeployEventHandler(controlledDispatcher_DeployEvent);
            watcher.StartWatching(controlledDispatcher);
            string subDir = Path.Combine(anotherAppFullPath, "foo");
            Directory.CreateDirectory(subDir);
            using (File.Create(Path.Combine (subDir, "foo.bar"))) {}
            dispatched = false;
            controlledDispatcher.PerformDispatch(null);
            Assert.IsFalse(dispatched);
        }

        [Test]
        public void UsesEventFiltersToAllowAnEventToPropagated ()
        {
            MockFileSystemEventFilter filter = new MockFileSystemEventFilter(true);
            watcher.StartWatching(dispatcher);
            watcher.AddAllowFilter (filter);
            string subDir = Path.Combine(appFullPath, "foo");
            Directory.CreateDirectory(subDir);
            using (File.Create(Path.Combine (appFullPath, "foo.bar"))) {}
            eventLatch.Acquire();            
            Assert.IsTrue(filter.called);
        }

        [Test]
        public void CanBeConfiguredToIncludeOrExcludePathsForEvents ()
        {
            string simple = "Data/Xml/watcher-simple.xml";
            XmlObjectFactory f = new XmlObjectFactory(new FileSystemResource(simple));
            f.RegisterSingleton(DefaultApplicationWatcherFactory.InjectedApplicationName, application);
            watcher = f["watcher"] as FileSystemApplicationWatcher;
            Assert.IsNotNull(watcher, 
                String.Format("test file [{0}] should define a file sistem resource!", simple));
            Assert.AreEqual(1, watcher.Excludes.Count);
            Assert.AreEqual(1, watcher.Includes.Count);

            watcher.StartWatching(dispatcher);

            // propagated
            string subDir = Path.Combine(appFullPath, "foo");
            Directory.CreateDirectory(subDir);
            using (File.Create(Path.Combine (subDir, "foo.bar"))) {}
            eventLatch.Acquire();            
            Assert.IsFalse(dispatched);
        }

        [Test]
        public void ByDefaultTheCaseIsIngoredButCanBeConfigured ()
        {
            Assert.IsTrue(watcher.IgnoreCase);
            watcher.IgnoreCase = false;
            Assert.IsFalse(watcher.IgnoreCase);
        }

        private void dispatcher_DeployEvent(object sender, DeployEventArgs args)
        {
            log.Debug("releasing latch");
            eventLatch.Release();
            log.Debug("latch released");
        }

        private void controlledDispatcher_DeployEvent(object sender, DeployEventArgs args)
        {
            dispatched = true;
            log.Debug(String.Format("application = {0}", args.Application.FullPath));
            log.Debug(String.Format("anotherAppFullPath = {0}", anotherAppFullPath));
            controlledEventLatch.Release();
        }
    }
}
