// Copyright (c) Microsoft Corporation. All rights reserved. 
//  
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. 
// THE ENTIRE RISK OF USE OR RESULTS IN CONNECTION WITH THE USE OF THIS CODE 
// AND INFORMATION REMAINS WITH THE USER. 
//  

/*********************************************************************
 * NOTE: A copy of this file exists at: WF\Common\Shared
 * The two files must be kept in [....].  Any change made here must also
 * be made to WF\Common\Shared\ValidationHelpers.cs
*********************************************************************/
namespace System.Workflow.Activities.Common
{
    #region Imports

    using System;
    using System.Diagnostics;
    using System.Collections;
    using System.Collections.Generic;
    using System.Reflection;
    using System.CodeDom.Compiler;
    using System.Workflow.ComponentModel.Design;
    using System.Workflow.ComponentModel.Compiler;
    using System.Collections.Specialized;
    using System.ComponentModel.Design.Serialization;
    using System.Workflow.ComponentModel;
    #endregion

    internal static class ValidationHelpers
    {
        #region Validation & helpers for ID and Type

        internal static void ValidateIdentifier(IServiceProvider serviceProvider, string identifier)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException("serviceProvider");

            SupportedLanguages language = CompilerHelpers.GetSupportedLanguage(serviceProvider);
            CodeDomProvider provider = CompilerHelpers.GetCodeDomProvider(language);
            if (language == SupportedLanguages.CSharp && identifier.StartsWith("@", StringComparison.Ordinal) ||
                language == SupportedLanguages.VB && identifier.StartsWith("[", StringComparison.Ordinal) && identifier.EndsWith("]", StringComparison.Ordinal) ||
                !provider.IsValidIdentifier(identifier))
            {
                throw new Exception(SR.GetString(SR.Error_InvalidLanguageIdentifier, identifier));
            }
        }

        internal static ValidationError ValidateIdentifier(string propName, IServiceProvider context, string identifier)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            ValidationError error = null;
            if (identifier == null || identifier.Length == 0)
                error = new ValidationError(SR.GetString(SR.Error_PropertyNotSet, propName), ErrorNumbers.Error_PropertyNotSet);
            else
            {
                try
                {
                    ValidationHelpers.ValidateIdentifier(context, identifier);
                }
                catch (Exception e)
                {
                    error = new ValidationError(SR.GetString(SR.Error_InvalidIdentifier, propName, e.Message), ErrorNumbers.Error_InvalidIdentifier);
                }
            }
            if (error != null)
                error.PropertyName = propName;
            return error;
        }

        internal static ValidationError ValidateNameProperty(string propName, IServiceProvider context, string identifier)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            ValidationError error = null;
            if (identifier == null || identifier.Length == 0)
                error = new ValidationError(SR.GetString(SR.Error_PropertyNotSet, propName), ErrorNumbers.Error_PropertyNotSet);
            else
            {
                SupportedLanguages language = CompilerHelpers.GetSupportedLanguage(context);
                CodeDomProvider provider = CompilerHelpers.GetCodeDomProvider(language);

                if (language == SupportedLanguages.CSharp && identifier.StartsWith("@", StringComparison.Ordinal) ||
                    language == SupportedLanguages.VB && identifier.StartsWith("[", StringComparison.Ordinal) && identifier.EndsWith("]", StringComparison.Ordinal) ||
                    !provider.IsValidIdentifier(provider.CreateEscapedIdentifier(identifier)))
                {
                    error = new ValidationError(SR.GetString(SR.Error_InvalidIdentifier, propName, SR.GetString(SR.Error_InvalidLanguageIdentifier, identifier)), ErrorNumbers.Error_InvalidIdentifier);
                }
            }
            if (error != null)
                error.PropertyName = propName;
            return error;
        }

        internal static ValidationErrorCollection ValidateUniqueIdentifiers(Activity rootActivity)
        {
            if (rootActivity == null)
                throw new ArgumentNullException("rootActivity");

            Hashtable identifiers = new Hashtable();
            ValidationErrorCollection validationErrors = new ValidationErrorCollection();
            Queue activities = new Queue();
            activities.Enqueue(rootActivity);
            while (activities.Count > 0)
            {
                Activity activity = (Activity)activities.Dequeue();
                if (activity.Enabled)
                {
                    if (identifiers.ContainsKey(activity.QualifiedName))
                    {
                        ValidationError duplicateError = new ValidationError(SR.GetString(SR.Error_DuplicatedActivityID, activity.QualifiedName), ErrorNumbers.Error_DuplicatedActivityID);
                        duplicateError.PropertyName = "Name";
                        duplicateError.UserData[typeof(Activity)] = activity;
                        validationErrors.Add(duplicateError);
                    }
                    else
                        identifiers.Add(activity.QualifiedName, activity);

                    if (activity is CompositeActivity && ((activity.Parent == null) || !Helpers.IsCustomActivity(activity as CompositeActivity)))
                    {
                        foreach (Activity child in Helpers.GetAllEnabledActivities((CompositeActivity)activity))
                            activities.Enqueue(child);
                    }
                }
            }

            return validationErrors;

        }
        #endregion

        #region Validation for Activity Ref order

        internal static bool IsActivitySourceInOrder(Activity request, Activity response)
        {
            if (request.Parent == null)
                return true;
            List<Activity> responsePath = new List<Activity>();
            responsePath.Add(response);
            Activity responseParent = response is CompositeActivity ? (CompositeActivity)response : response.Parent;
            while (responseParent != null)
            {
                responsePath.Add(responseParent);
                responseParent = responseParent.Parent;
            }

            Activity requestChild = request;
            CompositeActivity requestParent = request is CompositeActivity ? (CompositeActivity)request : request.Parent;
            while (requestParent != null && !responsePath.Contains(requestParent))
            {
                requestChild = requestParent;
                requestParent = requestParent.Parent;
            }

            if (requestParent == requestChild)
                return true;

            bool incorrectOrder = false;
            int index = (responsePath.IndexOf(requestParent) - 1);
            index = (index < 0) ? 0 : index; //sometimes parent gets added to the collection twice which causes index to be -1
            Activity responseChild = responsePath[index];

            if (requestParent == null || Helpers.IsAlternateFlowActivity(requestChild) || Helpers.IsAlternateFlowActivity(responseChild))
                incorrectOrder = true;
            else
            {
                for (int i = 0; i < requestParent.EnabledActivities.Count; i++)
                {
                    if (requestParent.EnabledActivities[i] == requestChild)
                        break;
                    else if (requestParent.EnabledActivities[i] == responseChild)
                    {
                        incorrectOrder = true;
                        break;
                    }
                }
            }
            return !incorrectOrder;
        }

        #endregion

        internal static ValidationErrorCollection ValidateObject(ValidationManager manager, object obj)
        {
            ValidationErrorCollection errors = new ValidationErrorCollection();
            if (obj == null)
                return errors;

            Type objType = obj.GetType();
            if (!objType.IsPrimitive && (objType != typeof(string)))
            {
                bool removeValidatedObjectCollection = false;
                Dictionary<int, object> validatedObjects = manager.Context[typeof(Dictionary<int, object>)] as Dictionary<int, object>;
                if (validatedObjects == null)
                {
                    validatedObjects = new Dictionary<int, object>();
                    manager.Context.Push(validatedObjects);
                    removeValidatedObjectCollection = true;
                }

                try
                {
                    if (!validatedObjects.ContainsKey(obj.GetHashCode()))
                    {
                        validatedObjects.Add(obj.GetHashCode(), obj);
                        try
                        {
                            Validator[] validators = manager.GetValidators(objType);
                            foreach (Validator validator in validators)
                                errors.AddRange(validator.Validate(manager, obj));
                        }
                        finally
                        {
                            validatedObjects.Remove(obj.GetHashCode());
                        }
                    }
                }
                finally
                {
                    if (removeValidatedObjectCollection)
                        manager.Context.Pop();
                }
            }

            return errors;
        }

        internal static ValidationErrorCollection ValidateActivity(ValidationManager manager, Activity activity)
        {
            ValidationErrorCollection errors = ValidationHelpers.ValidateObject(manager, activity);

            foreach (ValidationError error in errors)
            {
                if (!error.UserData.Contains(typeof(Activity)))
                    error.UserData[typeof(Activity)] = activity;
            }
            return errors;
        }

        internal static ValidationErrorCollection ValidateProperty(ValidationManager manager, Activity activity, object obj, PropertyValidationContext propertyValidationContext)
        {
            return ValidateProperty(manager, activity, obj, propertyValidationContext, null);
        }

        internal static ValidationErrorCollection ValidateProperty(ValidationManager manager, Activity activity, object obj, PropertyValidationContext propertyValidationContext, object extendedPropertyContext)
        {
            if (manager == null)
                throw new ArgumentNullException("manager");
            if (obj == null)
                throw new ArgumentNullException("obj");
            if (propertyValidationContext == null)
                throw new ArgumentNullException("propertyValidationContext");

            ValidationErrorCollection errors = new ValidationErrorCollection();

            manager.Context.Push(activity);
            manager.Context.Push(propertyValidationContext);
            if (extendedPropertyContext != null)
                manager.Context.Push(extendedPropertyContext);
            try
            {
                errors.AddRange(ValidationHelpers.ValidateObject(manager, obj));
            }
            finally
            {
                manager.Context.Pop();
                manager.Context.Pop();
                if (extendedPropertyContext != null)
                    manager.Context.Pop();
            }

            return errors;
        }
    }
}
