#region License

/*
 * Copyright  2002-2011 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

#region Imports

using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Net.Security;
using System.ServiceModel;

using NUnit.Framework;
using Spring.Core.IO;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Xml;
using System.Collections.Generic;

#endregion

namespace Spring.ServiceModel
{
    /// <summary>
    /// Unit tests for the ServiceExporter class.
    /// </summary>
    /// <author>Bruno Baia</author>
    [TestFixture]
    public sealed class ServiceExporterTests
    {
        ServiceExporter se = null;

        [SetUp]
        public void SetUp()
        {
            const string xml =
    @"<?xml version='1.0' encoding='UTF-8' ?>
        <objects xmlns='http://www.springframework.net'>
	        <object id='service' type='Spring.ServiceModel.ServiceExporterTests+Service, Spring.Services.Tests'/>
	        <object id='serviceWithMultipleInterfaces' type='Spring.ServiceModel.ServiceExporterTests+ServiceWithMultipleInterfaces, Spring.Services.Tests'/>
            <object id='decoratedService' type='Spring.ServiceModel.ServiceExporterTests+DecoratedService, Spring.Services.Tests'/>
            <object id='anotherService' type='Spring.ServiceModel.ServiceExporterTests+AnotherService, Spring.Services.Tests'/>
        </objects>";

            using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
            {
                IObjectFactory objectFactory = new XmlObjectFactory(new InputStreamResource(stream, string.Empty));
                se = new ServiceExporter();
                se.ObjectFactory = objectFactory;
            }
        }

        [Test]
        //SPRNET-1262
        public void ProxiesDeclarativeAttributeWithConstructorArguments()
        {
            XmlObjectFactory objectFactory = null;

            const string xml =
                @"<?xml version='1.0' encoding='UTF-8' ?>
                <objects xmlns='http://www.springframework.net'>
                <object id='theService' type='Spring.ServiceModel.ServiceExporterTests+Service, Spring.Services.Tests'/>	
                <object id='service' type='Spring.ServiceModel.ServiceExporter, Spring.Services'>
	                    <property name='TargetName' value='theService'/>
	                    <property name='TypeAttributes'>
                            <list>
                                <object type='System.ServiceModel.ServiceKnownTypeAttribute, System.ServiceModel' abstract='true'>
                                    <constructor-arg name='type' value='System.Collections.Generic.List<int>' />
                                </object>
                            </list>
                        </property>
                    </object>
                </objects>";

            using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
            {
                objectFactory = new XmlObjectFactory(new InputStreamResource(stream, string.Empty));
            }

            Type proxyType = (Type)objectFactory.GetObject("service");
            object[] attrs = proxyType.GetCustomAttributes(false);

            ServiceKnownTypeAttribute attrib = null;

            //loop thru all retreived attributes, attempting to find the one that is of the expected type
            for (int i = 0; i < attrs.Length; i++)
            {
                attrib = attrs[i] as ServiceKnownTypeAttribute;

                //break out of the loop once we find the one we want
                if (attrib != null)
                    break;
            }

            Assert.NotNull(attrib, String.Format("No attributes of the expecte type {0} were found.", typeof(ServiceKnownTypeAttribute)));
            Assert.AreEqual(typeof(List<int>), attrib.Type, "Property 'Type' on Target Attribute not set!");
        }

        [Test]
        public void NullConfig()
        {
            se.ObjectName = "NullConfig";
            Assert.Throws<ArgumentException>(() => se.AfterPropertiesSet(), "The TargetName property is required.");
        }

        [Test] // SPRNET-1416
        public void CallProxy()
        {
            se.ObjectName = "ProxiesContractInterface";
            se.TargetName = "service";
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            IContract proxy = Activator.CreateInstance(proxyType) as IContract;
            Assert.IsNotNull(proxyType);
            Assert.AreEqual("1979", proxy.SomeMethod(1979));
        }

        [Test]
        public void ProxiesContractInterface()
        {
            se.ObjectName = "ProxiesContractInterface";
            se.TargetName = "service";
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            Assert.IsTrue(typeof(IContract).IsAssignableFrom(proxyType));
        }

        [Test(Description = "http://jira.springframework.org/browse/SPRNET-1179")]
        public void ProxiesOnlyContractInterface()
        {
            se.ObjectName = "ProxiesOnlyContractInterface";
            se.TargetName = "serviceWithMultipleInterfaces";
            se.ContractInterface = typeof(IContract);
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            Assert.IsTrue(typeof(IContract).IsAssignableFrom(proxyType));
        }

        [Test(Description = "https://jira.springsource.org/browse/SPRNET-1464")]
        public void ProxiesInheritedContractInterface()
        {
            se.ObjectName = "ProxiesInheritedContractInterface";
            se.TargetName = "anotherService";
            se.ContractInterface = typeof(IInheritedContract);
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            Assert.IsTrue(typeof(IInheritedContract).IsAssignableFrom(proxyType));
        }

        [Test(Description = "http://jira.springframework.org/browse/SPRNET-1179")]
        public void ProxiesOnlyContractInterfaceFailsIfNoContractInterface()
        {
            se.ObjectName = "ProxiesOnlyContractInterface";
            se.TargetName = "serviceWithMultipleInterfaces";
            // se.ContractInterface = typeof (IContract);
            Assert.Throws<ArgumentException>(() => se.AfterPropertiesSet(), "ServiceExporter cannot export service type 'Spring.ServiceModel.ServiceExporterTests+ServiceWithMultipleInterfaces' as a WCF service because it implements multiple interfaces. Specify the contract interface to expose via the ContractInterface property.");
        }

        [Test]
        public void ProxyTypeEqualsObjectName()
        {
            se.ObjectName = "ProxyTypeEqualsObjectName";
            se.TargetName = "service";
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            Assert.AreEqual("ProxyTypeEqualsObjectName", proxyType.FullName);
        }

        [Test]
        public void CreatesServiceContractAttributeWithNoDecoratedClassAndMinimalConfig()
        {
            se.ObjectName = "CreatesServiceContractAttributeWithNoDecoratedClassAndMinimalConfig";
            se.TargetName = "service";
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            object[] attrs = proxyType.GetCustomAttributes(typeof(ServiceContractAttribute), true);
            Assert.IsNotEmpty(attrs);
            Assert.AreEqual(1, attrs.Length);

            ServiceContractAttribute sca = attrs[0] as ServiceContractAttribute;
            Assert.IsNull(sca.CallbackContract);
            Assert.AreEqual(typeof(IContract).FullName, sca.ConfigurationName);
            Assert.AreEqual(typeof(IContract).Name, sca.Name);
            Assert.IsNull(sca.Namespace);
            Assert.AreEqual(ProtectionLevel.None, sca.ProtectionLevel);
            Assert.AreEqual(SessionMode.Allowed, sca.SessionMode);
        }

        [Test]
        public void CreatesServiceContractAttributeWithNoDecoratedClassAndFullConfig()
        {
            se.ObjectName = "CreatesServiceContractAttributeWithNoDecoratedClassAndFullConfig";
            se.TargetName = "service";
            se.CallbackContract = typeof(IDisposable);
            se.ConfigurationName = "CustomConfigName";
            se.Name = "serviceName";
            se.Namespace = "http://Spring.Services.Tests";
            se.ProtectionLevel = ProtectionLevel.Sign;
            se.SessionMode = SessionMode.Required;

            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            object[] attrs = proxyType.GetCustomAttributes(typeof(ServiceContractAttribute), true);
            Assert.IsNotEmpty(attrs);
            Assert.AreEqual(1, attrs.Length);

            ServiceContractAttribute sca = attrs[0] as ServiceContractAttribute;
            Assert.AreEqual(se.CallbackContract, sca.CallbackContract);
            Assert.AreEqual(se.ConfigurationName, sca.ConfigurationName);
            Assert.AreEqual(se.Name, sca.Name);
            Assert.AreEqual(se.Namespace, sca.Namespace);
            Assert.AreEqual(se.ProtectionLevel, sca.ProtectionLevel);
            Assert.AreEqual(se.SessionMode, sca.SessionMode);
        }

        [Test]
        public void CreatesDefaultOperationContractAttributeWithNoDecoratedMethod()
        {
            se.ObjectName = "CreatesDefaultOperationContractAttributeWithNoDecoratedMethod";
            se.TargetName = "service";
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            MethodInfo method = proxyType.GetMethod("SomeMethod");
            Assert.IsNotNull(method);

            object[] attrs = method.GetCustomAttributes(typeof(OperationContractAttribute), true);
            Assert.IsNotEmpty(attrs);
            Assert.AreEqual(1, attrs.Length);
        }

        [Test]
        public void CreatesCustomOperationContractAttributeWithNoDecoratedMethod()
        {
            OperationContractAttribute oca1 = new OperationContractAttribute();
            oca1.Name = "MySomeMethod";

            se.ObjectName = "CreatesCustomOperationContractAttributeWithNoDecoratedMethod";
            se.TargetName = "service";
            se.MemberAttributes.Add("SomeMethod", oca1);
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            MethodInfo method = proxyType.GetMethod("SomeMethod");
            Assert.IsNotNull(method);

            object[] attrs = method.GetCustomAttributes(typeof(OperationContractAttribute), true);
            Assert.IsNotEmpty(attrs);
            Assert.AreEqual(1, attrs.Length);

            OperationContractAttribute oca2 = attrs[0] as OperationContractAttribute;
            Assert.AreEqual(oca1.Name, oca2.Name);
        }

        [Test]
        public void OverridesExistingServiceContractAttributeWithDecoratedClass()
        {
            se.ObjectName = "OverridesExistingServiceContractAttributeWithDecoratedClass";
            se.TargetName = "decoratedService";
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            object[] attrs = proxyType.GetCustomAttributes(typeof(ServiceContractAttribute), true);
            Assert.IsNotEmpty(attrs);
            Assert.AreEqual(1, attrs.Length);

            ServiceContractAttribute sca = attrs[0] as ServiceContractAttribute;
            Assert.IsNull(sca.Namespace);
        }

        [Test]
        public void UsesExistingOperationContractAttributeWithDecoratedMethod()
        {
            se.ObjectName = "UsesExistingOperationContractAttributeWithDecoratedMethod";
            se.TargetName = "decoratedService";
            se.AfterPropertiesSet();

            Type proxyType = se.GetObject() as Type;
            Assert.IsNotNull(proxyType);
            MethodInfo method = proxyType.GetMethod("SomeMethod");
            Assert.IsNotNull(method);

            object[] attrs = method.GetCustomAttributes(typeof(OperationContractAttribute), true);
            Assert.IsNotEmpty(attrs);
            Assert.AreEqual(1, attrs.Length);

            OperationContractAttribute oca = attrs[0] as OperationContractAttribute;
            Assert.AreEqual("MySomeMethod", oca.Name);
        }

        #region Test classes

        public interface IContract
        {
            string SomeMethod(int param);
        }

        public interface IOtherContract
        { }

        public class Service : IContract
        {
            public string SomeMethod(int param)
            {
                return param.ToString();
            }
        }

        public class ServiceWithMultipleInterfaces : Service, IOtherContract
        {
        }

        [ServiceContract(Namespace = "http://Spring.Services.Tests")]
        public class DecoratedService : IContract
        {
            [OperationContract(Name = "MySomeMethod")]
            public string SomeMethod(int param)
            {
                return param.ToString();
            }
        }

        public interface IInheritedContract : IContract
        {
            string AnotherMethod(int param);
        }

        public class AnotherService : IInheritedContract
        {
            public string SomeMethod(int param)
            {
                return param.ToString();
            }

            public string AnotherMethod(int param)
            {
                return param.ToString();
            }
        }

        //[ServiceContract(Namespace = "http://Spring.Services.Tests")]
        //public interface IDecoratedContract
        //{
        //    [OperationContract]
        //    string SomeMethod(int param);
        //}

        //public class AnotherService : IDecoratedContract
        //{
        //    public string SomeMethod(int param)
        //    {
        //        return param.ToString();
        //    }
        //}

        #endregion
    }
}