//===============================================================================
// Microsoft patterns & practices
// Web Service Software Factory
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.Design;
using EnvDTE;
using EnvDTE80;
using Microsoft.Practices.Common;
using Microsoft.Practices.RecipeFramework;
using Howest.Guidance.DataAccess.CodeGeneration.ObjectModel;
using Property = Howest.Guidance.DataAccess.CodeGeneration.ObjectModel.Property;

namespace Howest.Guidance.DataAccess.ValueProviders
{
    /// <summary/>
    public class EntityVP : ValueProvider, IAttributesConfigurable
    {
        /// <summary/>
        public const string ProjectArgumentAttribute = "ProjectArgument";

        string projectArgument;
        Project project;

        private Dictionary<CodeClass2, int> foundClasses;

        /// <summary>
        /// Called when [before actions].
        /// </summary>
        /// <param name="currentValue">The current value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns></returns>
        public override bool OnBeforeActions(object currentValue, out object newValue)
        {
            return OnBeginRecipe(currentValue, out newValue);
        }

        /// <summary>
        /// Called when [argument changed].
        /// </summary>
        /// <param name="changedArgumentName">Name of the changed argument.</param>
        /// <param name="changedArgumentValue">The changed argument value.</param>
        /// <param name="currentValue">The current value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns></returns>
        public override bool OnArgumentChanged(string changedArgumentName, object changedArgumentValue, object currentValue, out object newValue)
        {
            return OnBeginRecipe(currentValue, out newValue);
        }

        /// <summary>
        /// Called when [begin recipe].
        /// </summary>
        /// <param name="currentValue">The current value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns></returns>
        public override bool OnBeginRecipe(object currentValue, out object newValue)
        {
            ArrayList entities = new ArrayList();
            DTE dte = GetService<DTE>(true);
            foundClasses = new Dictionary<CodeClass2, int>();

            if (!string.IsNullOrEmpty(projectArgument))
            {
                IDictionaryService dictservice = GetService<IDictionaryService>(true);
                project = dictservice.GetValue(projectArgument) as Project;

                if (project != null)
                {
                    foreach (ProjectItem projectItem in project.ProjectItems)
                    {
                        ProcessProjectItem(projectItem, entities);
                    }
                }
            }
            else
            {
                ProjectItem item = dte.SelectedItems.Item(1).ProjectItem;

                if (item != null)
                {
                    ProcessProjectItem(item, entities);
                }
            }

            newValue = entities;
            return true;
        }

        #region Private Implementation

        private void ProcessProjectItem(ProjectItem item, ArrayList entities)
        {
            if (item.FileCodeModel != null)
            {
                foreach (CodeElement element in item.FileCodeModel.CodeElements)
                {
                    ProcessCodeElement(element, entities);
                }
            }
            foreach (ProjectItem childItem in item.ProjectItems)
            {
                ProcessProjectItem(childItem, entities);
            }
        }

        private void ProcessCodeElement(CodeElement element, ArrayList entities)
        {
            switch (element.Kind)
            {
            case vsCMElement.vsCMElementNamespace:
                ProcessNamespaceElement((CodeNamespace)element, entities);
                break;

            case vsCMElement.vsCMElementClass:
                ProcessClassElement((CodeClass2)element, entities);
                break;
            }
        }

        private void ProcessNamespaceElement(CodeNamespace codeNamespace, ArrayList entities)
        {
            foreach (CodeElement element in codeNamespace.Members)
            {
                ProcessCodeElement(element, entities);
            }
        }

        private void ProcessClassElement(CodeClass2 codeClass2, ArrayList entities)
        {
            if (!foundClasses.ContainsKey(codeClass2))
            {
                Entity entity = new Entity();
                entity.Name = codeClass2.Name;
                entity.TableName = codeClass2.Name;
                foreach (CodeElement partialClass in codeClass2.Parts)
                {
                    CodeClass2 partialCodeClass = (CodeClass2)partialClass;
                    foundClasses[partialCodeClass] = 1;
                    foreach (CodeElement child in partialCodeClass.Children)
                    {
                        ProcessClassChild(child, entity);
                    }
                }
                entities.Add(entity);
            }
        }


        private void ProcessClassChild(CodeElement child, Entity entity)
        {
            const string NULLABLE = "Nullable";

            if (child.Kind == vsCMElement.vsCMElementProperty)
            {
                CodeProperty codeProp = (CodeProperty)child;
                Property prop = new Property();
                prop.Name = codeProp.Name;
                prop.ColumnName = codeProp.Name;

                if (!string.IsNullOrEmpty(codeProp.Type.AsFullName))
                {
                    if (codeProp.Type.CodeType.Name == NULLABLE)
                    {
                        prop.Type = Type.GetType(codeProp.Type.CodeType.FullName.Replace("?", ""));
                        prop.IsNullable = true;
                    }
                    else
                    {
                        prop.Type = Type.GetType(codeProp.Type.AsFullName);
                        prop.IsNullable = false;
                    }
                }
                else
                {
                    try
                    {
                        if (codeProp.Type.ElementType != null)
                        {
                            prop.Type = Type.GetType(codeProp.Type.ElementType.AsFullName);
                        }
                    }
                    catch
                    {
                    }
                }
                entity.Properties.Add(prop);
            }
        }

        #endregion

        #region IAttributesConfigurable Members

        /// <summary>
        /// Configures the component using the dictionary of attributes specified
        /// in the configuration file.
        /// </summary>
        /// <param name="attributes">The attributes in the configuration element.</param>
        public void Configure(StringDictionary attributes)
        {
            if (attributes.ContainsKey(ProjectArgumentAttribute))
            {
                projectArgument = attributes[ProjectArgumentAttribute];
            }
        }

        #endregion
    }
}
