/*
  * 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.Net.Mail;
using System.Web.Services;
using System.Collections;
using System.Text;
using System.Configuration;
using dk.gov.oiosi.addressing;
using dk.gov.oiosi.uddi.category;
using dk.gov.oiosi.uddi;
using dk.gov.oiosi.uddi.identifier;
using dk.gov.oiosi.uddi.Services;
using dk.gov.oiosi.uddi.TModels;
using dk.gov.oiosi.common;

namespace dk.gov.oiosi.uddi {

    /// <summary>
    /// This class represents an inquiry to the UDDI inquiry API.
    /// It uses the Oiosi UDDI SDK to perform a set of inquiries to a UDDI.
    /// Creates a new instance per request.
    /// </summary>
    public class UddiInquiry {

        private Inquiry uddiLookupLibrary = new Inquiry();

        /// <summary>
        /// UDDI configuration
        /// </summary>
        protected UddiConfig pConfiguration;

        /// <summary>
        /// Constructor
        /// </summary>
        public UddiInquiry(UddiConfig configuration) {
            pConfiguration = configuration;
        }

        /// <summary>
        /// Returns true if this service is inactive or expired, according to its UDDI registration.
        /// All registrations on the UDDI are assumed to follow danish time zone conventions
        /// </summary>
        /// <param name="service">The service to check</param>
        /// <returns>Returns true if this service is inactive or expired, according to its UDDI registration.</returns>
        private bool IsInactiveOrExpired(BusinessService service) {
            DateTime activationDateUTC = GetActivationDate(service);
            DateTime expirationDateUTC = GetExpirationDate(service);
            DateTime nowUTC = DateTime.UtcNow;

            if (nowUTC > activationDateUTC && nowUTC < expirationDateUTC) return false;
            else return true;
        }

        /// <summary>
        /// Performs a synchronous inquiry to the UDDI inquiry API.
        /// The endpointKey is the endpoint lookup key.
        /// Use the TranslationOptions object to supply translation filters.
        /// Returns an InquiryResult class, which contains both the resolved endpoint as well as 
        /// UDDI identifiers and other information that can later be used for e.g. caching.
        /// 
        /// Throws an exception if errors are encountered during the inquiry.
        /// </summary>
        /// <param name="endpointKey">The key of the endpoint, e.g. an EAN number</param>
        /// <param name="inquiryParameters">Parameters of the inquiry, e.g. query filters</param>
        /// <param name="lookupPolicies">The policy of the lookup, i.e. query semantics</param>
        /// <returns>Returns an InquiryResult class, which contains both the resolved endpoint as well as 
        /// UDDI identifiers and other information that can later be used for e.g. caching.</returns>
        //public UDDIInquiryResult Inquire(
        public List<UddiLookupResponse> Inquire(
            IIdentifier endpointKey,
            LookupParameters inquiryParameters,
            UddiLookupClientPolicy lookupPolicies
        ) {

            List<UddiLookupResponse> result = new List<UddiLookupResponse>();

            // 1. Set categories and identifiers
            BusinessProcessRoleIdentifier processRoleIdentifier = inquiryParameters.RoleIdentifier;
            BusinessProcessRoleIdentifierType processRoleIdentifierType = inquiryParameters.RoleIdentifierType;
            BusinessProcessDefinitionReference processDefinitionReference = null;
            if (inquiryParameters.BusinessProcessDefinitionTModel != null) {
                processDefinitionReference =
                    new BusinessProcessDefinitionReference(
                       new UddiGuidId(inquiryParameters.BusinessProcessDefinitionTModel.ID, true
                    )
                );
            } 
            ArsEndpointKey arsEndpointKey = new ArsEndpointKey(inquiryParameters.EndpointKey);

            // 2. Get services
            serviceList serviceList = GetServices(
                arsEndpointKey, 
                inquiryParameters.ServiceContractTModel.ID, 
                inquiryParameters.EndpointKeyType, 
                inquiryParameters.ProfileConformanceClaim, 
                inquiryParameters.RegistrationConformanceClaim
            );

            if (serviceList.serviceInfos != null && serviceList.serviceInfos.Length > 0) {

                //3. get servicedetails for all found services
                string[] servicekeysFound = new string[serviceList.serviceInfos.Length];
                for (int i = 0; i < serviceList.serviceInfos.Length; i++) {
                    servicekeysFound[i] = serviceList.serviceInfos[i].serviceKey;
                }
                BusinessService[] services = GetServiceDetails(servicekeysFound);

                foreach (BusinessService service in services) {
                    // 4. We ignore this service if it is inactive (i.e. not activated yet) or expired
                    if (!IsInactiveOrExpired(service)) {

                        foreach (BindingTemplate template in service.BindingTemplates.Value) {

                            // 5. Get tModel instances:
                            TModel[] tModels = GetTModels(template);

                            // 6. add endpoint if values equals lookup paramteres
                            // Also validates tModel parameters
                            AddEndpointAddress(
                                endpointKey, tModels, service, template.AccessPoint,
                                service.Value.serviceKey, template.Value.bindingKey,
                                inquiryParameters, result
                            );

                        }
                    }

                }
            }

            return result;
        }


        private TModel[] GetTModels(BindingTemplate template) {
            List<string> tModelKeys = new List<string>();
            
            // Run through all tmodelinstanceinfos to check tmodel
            foreach (TModelInstanceInfo instanceinfo in template.TModelInstanceInfos) {
                tModelKeys.Add(instanceinfo.Value.tModelKey);
            }

            // Get the tModel details:
            get_tModelDetail detail = new get_tModelDetail();
            detail.tModelKey = tModelKeys.ToArray();
            return uddiLookupLibrary.GetDetail(detail);
        }

        #region methods


        /// <summary>
        /// Returns true if this process instance tModel matches the criteria  specified
        /// by the user
        /// </summary>
        /// <param name="tmodel">The tModel to check</param>
        /// <param name="parameters">Lookup parameters</param>
        /// <returns>True if criteria are matched</returns>
        private bool IsAcceptableProcessInstance(
            TModel tmodel,
            LookupParameters parameters
        ) {
            // 2. Check for businessProcessDefinitionReference:
            if (parameters.BusinessProcessDefinitionTModel != null) {

                BusinessProcessDefinitionReference procRef = new BusinessProcessDefinitionReference();
                KeyedReference procKeyref = tmodel.CategoryBag.GetCategoryByIdentifier(
                    procRef.CategoryID
                );
                if (procKeyref == null) return false;
                if (procKeyref.KeyValue.ToLower() != parameters.BusinessProcessDefinitionTModel.ID.ToLower()) {
                    return false;
                }
            }

            // 3. Check the role identifier type, if relevant:
            if (parameters.RoleIdentifierType != null) {
                BusinessProcessRoleIdentifierType roleIdType = new BusinessProcessRoleIdentifierType();
                KeyedReference roleTypeKeyref = tmodel.CategoryBag.GetCategoryByIdentifier(
                    roleIdType.CategoryID
                );
                if (roleTypeKeyref == null) return false;
                if (roleTypeKeyref.KeyValue != parameters.RoleIdentifierType.Value) {
                    return false;
                }
            }

            // 4. Check the Role identifier itself, if relevant:
            if (parameters.RoleIdentifier != null) {
                BusinessProcessRoleIdentifier roleId = new BusinessProcessRoleIdentifier();
                KeyedReference roleKeyref =
                    tmodel.IdentifierBagAsCategoryBagObject.GetCategoryByIdentifier(
                        roleId.IdentifierID
                );
                if (roleKeyref == null) return false;
                if (roleKeyref.KeyValue.ToLower() != parameters.RoleIdentifier.Value.ToLower()) {
                    return false;
                }
            }

            // 5. All tests passed, return true
            return true;
        }

        /// <summary>
        /// Returns true if this TModel represents a valid process instance
        /// </summary>
        /// <param name="tmodel">The TModel to check</param>
        /// <param name="parameters">The lookup parameters</param>
        /// <returns>True if valid</returns>
        private bool IsProcessInstance(TModel tmodel, LookupParameters parameters) {
            if (tmodel == null || tmodel.CategoryBag == null && tmodel.CategoryBag.Value.Items.Length < 1){
                return false;
            }

            // 1. Check registration conformance
            RegistrationConformanceClaim regConfClaim = new RegistrationConformanceClaim();
            KeyedReference confClaimKeyref = tmodel.CategoryBag.GetCategoryByIdentifier(regConfClaim.CategoryID);
            if (confClaimKeyref == null) return false;
            if (confClaimKeyref.KeyValue != parameters.RegistrationConformanceClaim.Value) {
                return false;
            }

            // 2. Check that the businessProcessDefinitionReference category exists:
            BusinessProcessDefinitionReference procRef = new BusinessProcessDefinitionReference();
            KeyedReference procKeyref = tmodel.CategoryBag.GetCategoryByIdentifier(
                procRef.CategoryID
            );

            if (procKeyref == null) return false;

            return true;
        }

        /// <summary>
        /// Checks tModel parameters of a binding, e.g. if the required process-related
        /// categories are set correctly. At least one process-related tModel must 
        /// meet all the specified criteria.
        /// </summary>
        /// <param name="tmodels">The tModels for the binding to check</param>
        /// <param name="parameters">The lookup parameters specifying the criteria</param>
        /// <returns>True if criteria are met</returns>
        private bool MeetsTModelCriteria(
            TModel[] tmodels,
            LookupParameters parameters
        ) {
            // 1. Have any process-related criteria been set?
            if (parameters.BusinessProcessDefinitionTModel == null &&
                parameters.RoleIdentifier == null &&
                parameters.RoleIdentifierType == null
            ) {
                // If no criteria are set, return true
                return true;
            }

            foreach (TModel tm in tmodels) {
                if (IsProcessInstance(tm, parameters)) {
                    if (IsAcceptableProcessInstance(tm, parameters)) {
                        return true;
                    };
                } 
            }
            return false;
        }

        /// <summary>
        /// Creates endpointinformation, if the category value are as expected,
        /// and adds it to the list of results
        /// </summary>
        private void AddEndpointAddress(
            IIdentifier endpointKey,
            TModel[] tmodels, 
            BusinessService service, 
            AccessPoint accessPoint, 
            string serviceKey, 
            string bindingKey, 
            LookupParameters parameters, 
            List<UddiLookupResponse> results
        ) {
            // 1. Check properties of associated tModels:
            if (!MeetsTModelCriteria(tmodels, parameters)) {
                // Do not add to the result set:
                return;
            }

            // 2. make a new endpointaddress
            EndpointAddress endpointAddress = IdentifierUtility.GetEndpointAddressFromString(
                accessPoint.AccessEndpointString
            );

            // 3. Check against address type filter:
            if (!IsAcceptableAddressType(endpointAddress, parameters)) {
                return;
            }

            // 4. get authenticationrequired
            bool autRequired = GetAuthenticationRequired(service);

            // 5. get endpoint activation and expiration time
            DateTime activationUTC = GetActivationDate(service);
            DateTime expirationUTC = GetExpirationDate(service);

            // 6. get endpoint certificate subject
            string cert = GetCertificate(service);

            // 7. get terms of use url:
            Uri termsOfUse = GetTermsOfUseUrl(service);

            // 8. Get contact address:
            MailAddress contactMail = GetContactMail(service);

            // 9. Get version:
            Version version = GetVersion(service);

            // 10. Get newer version reference:
            UddiId newerVersion = GetNewerVersion(service);

            // 11. make a new UddiLookupResponse
            UddiLookupResponse resp = new UddiLookupResponse(
                endpointKey,
                endpointAddress,
                activationUTC,
                expirationUTC,
                new dk.gov.oiosi.security.CertificateSubject(cert),
                termsOfUse, 
                contactMail, 
                version,
                newerVersion
             );
                
            // 12. add the endpointinformation to the result object
            results.Add(resp);
        }

        /// <summary>
        /// Checks if an endpoint address is of a type that is on the endpoint filter list.
        /// </summary>
        /// <param name="address">The address to check</param>
        /// <param name="parameters">The parameter object holding the filter</param>
        /// <returns>True if the address type is on the list</returns>
        private bool IsAcceptableAddressType(
            EndpointAddress address,
            LookupParameters parameters
        ) {
            if (parameters.AddressTypeFilter == null || parameters.AddressTypeFilter.Count < 1) {
                return true;
            }
            if (address is EndpointAddressSMTP) {
                foreach (EndpointAddressTypeCode typecode in parameters.AddressTypeFilter) {
                    if (typecode == EndpointAddressTypeCode.email) return true;
                }
            } else if (address is EndpointAddressHttp) {
                foreach (EndpointAddressTypeCode typecode in parameters.AddressTypeFilter) {
                    if (typecode == EndpointAddressTypeCode.http) return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Gets the tmodel from uddi registry
        /// </summary>
        private TModel GetTModelDetail(string tmodelKey) {

            TModel[] foundTmodels = null;
            try {
                //1. get tmodeldetail for the tmodel
                GetTModelDetail tmodelDetail = new GetTModelDetail(tmodelKey);
                foundTmodels = uddiLookupLibrary.GetDetail(tmodelDetail.Value);
            }
            catch {
            }
            return foundTmodels[0];
        }        

        /// <summary>
        /// Gets certificate from category value
        /// </summary>
        private string GetCertificate(BusinessService service) {

            string cert = "";

            CategoryBag bag = service.CategoryBag;
            KeyedReference certificate = bag.GetCategoryByName(new EndpointCertificate().CategoryName);
            
            if (certificate != null) {
                cert = certificate.KeyValue;
            }
            return cert;
        }


        private Uri GetTermsOfUseUrl(BusinessService service) {
            TermsOfUseUrl terms = new TermsOfUseUrl();
            KeyedReference termsRef = service.CategoryBag.GetCategoryByIdentifier(terms.CategoryID);
            if (termsRef != null) {
                if (!String.IsNullOrEmpty(termsRef.KeyValue)) {
                    return new Uri(termsRef.KeyValue);
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        private MailAddress GetContactMail(BusinessService service) {
            EndpointContactEmail mail = new EndpointContactEmail();
            KeyedReference mailRef = service.CategoryBag.GetCategoryByIdentifier(mail.CategoryID);
            if (mailRef != null) {
                if (!String.IsNullOrEmpty(mailRef.KeyValue)) {
                    return new MailAddress(mailRef.KeyValue);
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }


        private Version GetVersion(BusinessService service) {
            Version v = null;
            VersionMajor major = new VersionMajor();
            major = (VersionMajor)GetCategory(service, major);
            if (major == null) return null;

            VersionMinor minor = new VersionMinor();
            minor = (VersionMinor)GetCategory(service, minor);

            VersionRevision rev = new VersionRevision();
            rev = (VersionRevision)GetCategory(service, rev);

            // 2. Create the version number
            int majorInt = 0;
            int minorInt = 0;
            int revisionInt = 0;

            majorInt = Int32.Parse(major.Value);
            if (minor != null) minorInt = Int32.Parse(minor.Value);
            if (rev != null) revisionInt = Int32.Parse(rev.Value);

            v = new Version(majorInt, minorInt, revisionInt);
            return v;
        }

        private UddiId GetNewerVersion(BusinessService service) {
            NewerVersionReference newRef = new NewerVersionReference();
            KeyedReference newVersKeyref = service.CategoryBag.GetCategoryByIdentifier(newRef.CategoryID);
            if (newVersKeyref != null) {
                if (!String.IsNullOrEmpty(newVersKeyref.KeyValue)) {
                    return dk.gov.oiosi.common.IdentifierUtility.GetUddiIDFromString(
                        newVersKeyref.KeyValue
                    );
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }


        /// <summary>
        /// Gets an ARS category by the category ID of the 'categoryObject' 
        /// parameter. Note that we need an actual ArsCategory descendant 
        /// object to get the category identifier. If no match is found, null is returned.
        /// </summary>
        /// <param name="service">The business service that holds the category bag to search</param>
        /// <param name="category">The category object from which to get the category ID.
        /// Another object, with key and value set, is returned as a result.</param>
        /// <returns></returns>
        private ArsCategory GetCategory(BusinessService service, ArsCategory category) {
            if (category == null) return null;
            KeyedReference keyRef = service.CategoryBag.GetCategoryByIdentifier(category.CategoryID);
            if (keyRef == null) return null;
            category.SetCategoryValue(keyRef.KeyName, keyRef.KeyValue);
            return category;
        }

        /// <summary>
        /// Returns a DateTime representation of either an endpoint expiration
        /// or activation date, in UTC
        /// </summary>
        /// <param name="datestring">The date of an endpoint expiration or activation</param>
        /// <param name="getActivationDate">True if the date represents an activation date,
        /// false if expiration date. This influences which default is returned, if no date
        /// is present in the string (DateTime.MinValue for activation date, 
        /// DateTime.MaxValue for expiration time</param>
        /// <returns>
        /// Returns a DateTime representation of either an endpoint expiration
        /// or activation date, in UTC
        /// </returns>
        private DateTime GetDatetimeFromLifetimeDates(string datestring, bool getActivationDate) {
            if (String.IsNullOrEmpty(datestring)) {
                // Then set default date:
                if (getActivationDate) return DateTime.MinValue;
                else return DateTime.MaxValue;
            } else if (datestring.Length < 20) {
                // Heuristic, treat as local date:
                return DateTime.Parse(datestring);
            } else {
                // Heuristic, treat as UTC date:
                return DateTime.Parse(
                    datestring,
                    System.Globalization.CultureInfo.InvariantCulture,
                    System.Globalization.DateTimeStyles.AdjustToUniversal
                );
            }
                
            // 1. split the datestring up in year, month and date and return a DateTime object
            /*string year = "";
            string month = "";
            string day = "";

            if (datestring.Trim().Length == 19){
                DateTime parsedDateTime = 
                    DateTime.ParseExact(datestring, "yyyy-MM-dd HH:MM:SS", System.Globalization.DateTimeFormatInfo.CurrentInfo);
                return parsedDateTime;
            } else if (datestring.Trim().Length == 10){
                DateTime parsedDate =
                    DateTime.ParseExact(datestring, "yyyy-MM-dd", System.Globalization.DateTimeFormatInfo.CurrentInfo);
                return parsedDate;
            } 
           

            // string[] values = datestring.Split('-');
            //if (values.Length == 3) {
            //    year = values[0];
            //    month = values[1];
            //    day = values[2];
            //
            //    return new DateTime(Convert.ToInt32(year), Convert.ToInt32(month), Convert.ToInt32(day));
            //  }
            else {
                if (getActivationDate) return DateTime.MinValue;
                else return DateTime.MaxValue;
            }*/
        }

        /// <summary>
        /// Gets endpoint activation date from category value, in UTC
        /// </summary>
        private DateTime GetActivationDate(BusinessService service) {

            DateTime activation = DateTime.MinValue;

            CategoryBag bag = service.CategoryBag;
            KeyedReference act = bag.GetCategoryByName(new EndpointActivationDate().CategoryName);

            if (act != null) {
                activation = GetDatetimeFromLifetimeDates(act.KeyValue, false);
            }
            return activation;
        }

        /// <summary>
        /// Gets endpoint expiration date from category value, in UTC
        /// </summary>
        private DateTime GetExpirationDate(BusinessService service) {

            DateTime expiration = DateTime.MaxValue;

            CategoryBag bag = service.CategoryBag;
            KeyedReference exp = bag.GetCategoryByName(new EndpointExpirationDate().CategoryName);

            if (exp != null) {
                expiration = GetDatetimeFromLifetimeDates(exp.KeyValue, false);
            }
            return expiration;
        }

       
        /// <summary>
        /// Gets authenticationRequired bool from category value
        /// </summary>
        private bool GetAuthenticationRequired(BusinessService service) {

            bool authenticationRequired = false;

            CategoryBag bag = service.CategoryBag;
            KeyedReference autRequired = bag.GetCategoryByName(new AuthenticationRequired().CategoryName);

            if (autRequired != null) {
                if (autRequired.KeyValue == "http://oio.dk/profiles/OWSA/modelT/1.0/UDDI/authenticationRequired/") {
                    authenticationRequired = true;
                }
            }
            return authenticationRequired;
        }

        /// <summary>
        /// Calls get servicedetails from a list of servicekeys
        /// </summary>
        private BusinessService[] GetServiceDetails(string[] servicekeysFound) {

            //1. get_servicedetail foreach service found
            BusinessService[] services = null;
            try {
                GetServiceDetail serviceDetail = new GetServiceDetail(servicekeysFound);
                services = uddiLookupLibrary.GetDetail(serviceDetail.Value);
            }
            catch {
                throw;
            }

            return services;
        }

        /// <summary>
        /// Calls findservice with relevant parameters
        /// </summary>
        private serviceList GetServices(
            ArsEndpointKey arsEndpointKey, 
            string centralServiceDefinitionTModelKey,
            EndpointKeytype endpointKeyType,
            ConformanceClaim profileConformanceClaim,
            RegistrationConformanceClaim registrationConformanceClaim
        ) {

            //1. set categories
            CategoryBag serviceCategories = new CategoryBag();
            if (profileConformanceClaim != null) {
                serviceCategories.AddCategory(profileConformanceClaim.GetAsKeyedReference());
            }
            if (registrationConformanceClaim != null) {
                serviceCategories.AddCategory(registrationConformanceClaim.GetAsKeyedReference());
            }

            if (endpointKeyType != null) {
                serviceCategories.AddCategory(endpointKeyType.GetAsKeyedReference());
            }

            // NOTE: adding the EAN key LAST in the category bag seems to increase systinet 
            // UDDI registry performance dramatically. 
            serviceCategories.AddCategory(arsEndpointKey.GetAsKeyedReference());

            FindQualifers[] findQualifiers = new FindQualifers[1];
            findQualifiers[0] = FindQualifers.andAllKeys;
            FindService findService = new FindService(findQualifiers, serviceCategories);

            //add a reference to the central service definition tmodel
            string[] centralServiceDefinitionKey = { centralServiceDefinitionTModelKey };
            findService.Value.tModelBag = centralServiceDefinitionKey;
            
            //3. get services
            serviceList Slist = uddiLookupLibrary.Find(findService);

            return Slist;
        }


        #endregion methods
    }
}