/*
  * The contents of this file are subject to the Mozilla Public
  * License Version 1.1 (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.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an
  * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
  * or implied. See the License for the specific language governing
  * rights and limitations under the License.
  *
  *
  * The Original Code is .NET RASP toolkit.
  *
  * The Initial Developer of the Original Code is Accenture and Avanade.
  * Portions created by Accenture and Avanade are Copyright (C) 2007
  * Danish National IT and Telecom Agency (http://www.itst.dk). 
  * All Rights Reserved.
  *
  * Contributor(s):
  *   Gert Sylvest (gerts@avanade.com)
  *   Patrik Johansson (p.johansson@accenture.com)
  *   Michael Nielsen (michaelni@avanade.com)
  *   Dennis Søgaard (dennis.j.sogaard@accenture.com)
  *   Ramzi Fadel (ramzif@avanade.com)
  *   Mikkel Hippe Brun (mhb@itst.dk)
  *   Finn Hartmann Jordal (fhj@itst.dk)
  *   Christian Lanng (chl@itst.dk)
  *
  */
using System;
using System.Collections.Generic;
using System.Text;

using dk.gov.oiosi.uddi.ars;
using dk.gov.oiosi.uddi;

namespace dk.gov.oiosi.oioublProfileLibrary {

    /// <summary>
    /// Wrapper class for the ArsEndpoint class.
    /// An oioubl service is similar to an ARS endpoint, 
    /// but with some built-in enforcements.
    /// - Only two bindings are allowed
    /// - The bindings must reference the same process definitions
    /// - The bindings must reference the same service definition
    /// - The bindings must use different protocols (ie, http, mtp, ...)
    /// 
    /// When creating an instance based on an existing ARS endpoint,
    /// an exception is thrown if these conditions are not met.
    /// </summary>
    internal class OioublService : IOioublService {
        private ArsEndpoint _arsEndpoint;

        public ArsEndpoint ArsEndpoint {
            get { return _arsEndpoint; }
            set { _arsEndpoint = value; }
        }
        private List<OioublBinding> _oioublBindings = new List<OioublBinding>();
        private IOioublBusinessEntity _businessEntity;

        internal IOioublBusinessEntity BusinessEntity {
            get { return _businessEntity; }
            set { _businessEntity = value; }
        }

        /// <summary>
        /// Returns all process definitions referenced by the service
        /// </summary>
        internal IEnumerable<OioublProcessDefinition> ProcessDefinitions {
            get { return GetProcessDefinitions(); }
        }

        /// <summary>
        /// The Uddi ID for the Service.
        /// This returns the Uddi Id for the wrapped ArsEndpoint.
        /// </summary>
        internal UddiId UddiId {
            get { return _arsEndpoint.ID; }
        }

        /// <summary>
        /// Returns the ServiceDefinition referenced by the service
        /// </summary>
        internal OioublServiceDefinition ServiceDefinition {
            get { return GetServiceDefinition(); }
        }

        /// <summary>
        /// Constructor for the OioublService.
        /// The constructor validates the ArsEndpoint and throws an exception if the endpoint
        /// doesn't meet the requirements of an OioublService.
        /// </summary>
        /// <param name="arsEndpoint">The ArsEndpoint to wrap</param>
        /// <param name="businessEntity">The BusinessEntity that owns the service</param>
        internal OioublService(ArsEndpoint arsEndpoint, IOioublBusinessEntity businessEntity) {
            _arsEndpoint = arsEndpoint;
            _businessEntity = businessEntity;
            Initialize();
        }


        /// <summary>
        /// Returns true if the service has a reference to the provided process definition.
        /// </summary>
        /// <param name="processDefinition"></param>
        /// <returns></returns>
        internal bool HasProcess(OioublProcessDefinition processDefinition) {
            foreach (OioublProcessDefinition definition in ProcessDefinitions) {
                if (definition.Equals(processDefinition)) return true;
            }
            return false;
        }

        /// <summary>
        /// Returns true if the service has a reference to the provided process definition, and
        /// if the role of the process matches the role of the profile role        /// </summary>
        /// <param name="profile"></param>
        /// <returns></returns>
        public bool HasProcess(IOioublProfile profile) {
            foreach (OioublProcessDefinition definition in ProcessDefinitions) {
                if (definition.Equals(profile) && 
                    definition.ProcessInstance.ProcessRoleIdentifier.Value.ToLower() 
                    == profile.BusinessEntityRole.ToString().ToLower())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Adds an association to the process definition
        /// A process instance with a reference is created and added to the binding
        /// </summary>
        /// <param name="profile">The profile to add</param>
        internal void AddProcessDefinition(IOioublProfile profile) {
            foreach (OioublBinding oioublBinding in _oioublBindings) {
                oioublBinding.AddProcess(profile);
            }
        }

        /// <summary>
        /// Returns true if the Service references the provided ServiceDefinition.
        /// </summary>
        /// <param name="serviceDefinition"></param>
        /// <returns></returns>
        public bool HasServiceDefinition(OioublServiceDefinition serviceDefinition) {
            if (ServiceDefinition.UddiId.ID == serviceDefinition.UddiId.ID) return true;
            return false;
        }

        /// <summary>
        /// Adds a binding to the service
        /// </summary>
        /// <param name="oioublProfile">The profile that the binding supports</param>
        /// <param name="serviceDefinition">The type of service the binding supports</param>
        /// <param name="addressType">The address type of the binding (http, smtp, ...)</param>
        /// <param name="addressUri">The actual address URI of the binding</param>
        internal void AddBinding(IOioublProfile oioublProfile, 
            OioublServiceDefinition serviceDefinition, 
            OioublEndpointAddressType addressType,
            string addressUri) {

            OioublBinding oioublBinding = OioublBindingFactory.CreateOioublBinding(
                BusinessEntity,
                oioublProfile,
                serviceDefinition,
                addressType,
                addressUri);

            _oioublBindings.Add(oioublBinding);
            AddBindingToArsEndpoint(oioublBinding.ArsBinding);
        }

        /// <summary>
        /// Deletes the service.
        /// Behind the scenes:
        /// Deletes the ArsEndpoint, the ArsBindings and the ArsProcessInstances by calling the arsEndpoint.Delete() method.
        /// </summary>
        internal void Delete() {
            _arsEndpoint.Delete();
        }

        /// <summary>
        /// Removes the process definition from the service.
        /// Behind the scenes:
        /// The process instance that references the process definition is deleted.
        /// All bindings that has a reference to the process instance, will have the reference removed.
        /// </summary>
        /// <param name="oioublProfile"></param>
        internal void RemoveProcessDefinition(IOioublProfile oioublProfile) {
            foreach (OioublBinding oioublBinding in _oioublBindings) {
                oioublBinding.RemoveProcessDefinition(oioublProfile);
            }
        }

        /// <summary>
        /// Returns true if the service has more than one service definition
        /// </summary>
        /// <returns></returns>
        internal bool HasMoreThanOneProcessDefinition() {
            if (_oioublBindings.Count > 0) {
                List<OioublProcessDefinition> processDefinitions = 
                    (List<OioublProcessDefinition>)_oioublBindings[0].ProcessDefinitions;
                if (processDefinitions.Count>1) return true;
            }
            return false;
        }

        /// <summary>
        /// Saves the service
        /// </summary>
        internal void Save() {
            _arsEndpoint.Bindings = GetArsBindingSet();
            _arsEndpoint.Save();
        }

        /// <summary>
        /// Returns true if the service has the provided endpoint key and endpoint address.
        /// </summary>
        /// <param name="endpointKey"></param>
        /// <param name="endpointAddress"></param>
        /// <returns></returns>
        internal bool HasEndpoint(OioublEndpointKey endpointKey, OioublEndpointAddress endpointAddress) {
            bool endpointKeyIsEqual = endpointKey.Equals(_arsEndpoint.EndpointKeyType.GetEndpointKeyTypeCode(), _arsEndpoint.EndpointKey);
            bool endpointAddressIsEqual = HasEndpointAddress(endpointAddress);

            if (endpointKeyIsEqual && endpointAddressIsEqual) return true;
            return false;
        }


        # region Helper methods

        private ArsBindingSet GetArsBindingSet() {
            ArsBindingSet arsBindingSet = new ArsBindingSet();
            foreach (OioublBinding oioublBinding in _oioublBindings) {
                arsBindingSet.Add(oioublBinding.ArsBinding);
            }
            return arsBindingSet;
        }

        private bool HasEndpointAddress(OioublEndpointAddress endpointAddress) {

            bool hasHttpAddress = false;
            if (endpointAddress.HttpEndpointAddress != null) {
                hasHttpAddress = HasEndpointAddress(endpointAddress.HttpEndpointAddress.AbsoluteUri);
            }

            bool hasSmtpAddress = false;
            if (endpointAddress.SmtpEndpointAddress != null) {
                hasSmtpAddress = HasEndpointAddress(endpointAddress.SmtpEndpointAddress.AbsoluteUri);
            }

            int bindingCount = _oioublBindings.Count;
            if (hasHttpAddress && hasSmtpAddress) return true;
            if (hasHttpAddress == false && hasSmtpAddress == false && bindingCount == 0) return true;
            if (hasHttpAddress && hasSmtpAddress == false && bindingCount == 1) return true;
            if (hasSmtpAddress && hasHttpAddress == false && bindingCount == 1) return true;

            return false;
        }

        private bool HasEndpointAddress(string endpointAddressUri) {
            foreach (OioublBinding binding in _oioublBindings) {
                bool hasBinding = binding.HasEndpointAddressUri(endpointAddressUri);
                if (hasBinding) return true;
            }
            return false;
        }


        private void AddBindingToArsEndpoint(ArsBinding arsBinding) {
            ArsBindingSet arsBindingSet;
            if (_arsEndpoint.Bindings == null) {
                arsBindingSet = new ArsBindingSet();
            }
            else {
                arsBindingSet = _arsEndpoint.Bindings;
            }
            arsBindingSet.Add(arsBinding);
            _arsEndpoint.Bindings = arsBindingSet;
        }

        private void Initialize() {
            SetupBindings();
            VerifyThatAtMostTwoBindingsExist();
            VerifyThatBindingsHaveSimilarProcessDefinitions();
            VerifyThatBindingsHaveDifferentProtocols();
            VerifyThatBindingsHaveSimilarServiceReferences();
        }


        private void SetupBindings() {
            if (_arsEndpoint.Bindings == null || _arsEndpoint.Bindings.Bindings == null) return;

            foreach (ArsBinding arsBinding in _arsEndpoint.Bindings.Bindings) {
                _oioublBindings.Add(new OioublBinding(arsBinding));
            }
        }

        private IEnumerable<OioublProcessDefinition> GetProcessDefinitions() {
            // The UDDI model allows for each binding to have different process definition instances/references
            // In an oioubl service each binding must have the same process definition references,
            // but different process instances.
            if (_oioublBindings.Count > 0) return _oioublBindings[0].ProcessDefinitions;
            throw new ApplicationException("No binding found");
        }

        private void VerifyThatAtMostTwoBindingsExist() {
            int bindingCount = _oioublBindings.Count;
            if (bindingCount > 2) throw new ApplicationException("Too many bindings in service, only two allowed");
        }

        private void VerifyThatBindingsHaveSimilarServiceReferences() {
            if (_oioublBindings.Count == 0) return;

            OioublBinding firstBinding = _oioublBindings[0];

            int index = 0;
            foreach (OioublBinding binding in _oioublBindings) {
                if (index > 0) {
                    if (!firstBinding.CompareServiceDefinition(binding)) {
                        throw new ApplicationException("Bindings don't have the same service references");
                    }
                }
                index++;
            }
        }


        private void VerifyThatBindingsHaveDifferentProtocols() {
            List<string> observedTypes = new List<string>();
            foreach (OioublBinding binding in _oioublBindings) {
                if (observedTypes.Contains(binding.BindingProtocolUddiId)) {
                    string bindingDescription = "";
                    if (binding.ArsBinding != null && binding.ArsBinding.Description != null
                        && binding.ArsBinding.Description.Text != null) {
                        bindingDescription = binding.ArsBinding.Description.Text;
                    }
                    throw new ApplicationException("More than one binding share the same protocol - see binding with description '" + bindingDescription + "'");
                }
                observedTypes.Add(binding.BindingProtocolUddiId);
            }
        }

        private void VerifyThatBindingsHaveSimilarProcessDefinitions() {
            // Each binding can have one or more process definitons.
            // The process definitions must be the exact same for all bindings
            if (_oioublBindings.Count == 0) return;

            OioublBinding firstBinding = _oioublBindings[0];
            
            int index = 0;
            foreach (OioublBinding binding in _oioublBindings) {
                if (index > 0) {
                    bool areEqual = firstBinding.CompareProcesses(binding);
                    if (!areEqual) throw new ApplicationException("Process definitions are not equal in all bindings");
                }
                index++;
            }
        }

        private OioublServiceDefinition GetServiceDefinition() {
            if (_oioublBindings.Count == 0) return null;

            OioublBinding firstBinding = _oioublBindings[0];
            return new OioublServiceDefinition(firstBinding.ServiceDefinitionUddiId);
        }

        # endregion

    }
}
