using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Autodesk.Revit.DB;
// ================= DATA HOLDING CLASS =============================================================================================
    /// 
    /// This class contains information discovered about a (shared or non-shared) project parameter 
    /// 
    internal class ProjectParameterData
    {
        public Definition Definition = null;
        public ElementBinding Binding = null;
        public bool IsSharedStatusKnown = false;  // Will probably always be true when the data is gathered
        public bool IsShared = false;
        public string GUID = null;
    }
// ================= MAIN DATA COLLECTION CODE (HELPER FUNCTIONS ARE LISTED BELOW) ==================================================
                // Get the (singleton) element that is the ProjectInformation object.  It can only have instance
                // parameters bound to it, but it's always guaranteed to exist.
                Element projectInfoElement = new FilteredElementCollector(projectDocument).OfCategory(BuiltInCategory.OST_ProjectInformation).FirstElement();
                // Get the first wall type element.  It can only have type
                // parameters bound to it, but there's always guaranteed to be at least one of these.
                Element firstWallTypeElement = new FilteredElementCollector(projectDocument).OfCategory(BuiltInCategory.OST_Walls).WhereElementIsElementType().FirstElement();
                CategorySet categories = null;
                Parameter foundParameter = null;
                // Get the list of information about all project parameters.  
                List origProjectParametersData = GetProjectParameterData(projectDocument);
                // In order to be able to query whether or not a project parameter is shared or not, and if it is shared
                // then what it's GUID is, we must ensure it exists in the Parameters collection of an element.
                // This is because we can not query this information directly from the project parameter bindings object.
                //
                // So each project parameter will attempt to be temporarily bound to a known object so 
                // a Parameter object created from it will exist and can be queried for this additional information.
                foreach (ProjectParameterData projectParameterData in origProjectParametersData)
                {
                    if (projectParameterData.Definition != null)
                    {
                        categories = projectParameterData.Binding.Categories;
                        if (!categories.Contains(projectInfoElement.Category))
                        {
                            // This project parameter is not already bound to the ProjectInformation category,
                            // so we must temporarily bind it so we can query that object for it.
                            using (Transaction tempTransaction = new Transaction(projectDocument, "Temporary"))
                            {
                                tempTransaction.Start();
                                // Try to bind the project parameter do the project information category.
                                if (AddProjectParameterBinding(projectDocument,
                                                               projectParameterData,
                                                               projectInfoElement.Category))
                                {
                                    // successfully bound
                                    foundParameter = projectInfoElement.get_Parameter(projectParameterData.Definition);
                                    if (foundParameter == null)
                                    {
                                        // Must be a shared type parameter, which the API reports that it binds
                                        // to the project information category via the API, but doesn't 
                                        // ACTUALLY bind to the project information category.  (Sheesh!)
                                        // So we must use a different, type based object known to exist, and try again
                                        if (!categories.Contains(firstWallTypeElement.Category))
                                        {
                                            // Add it to walls category as we did with project info for the others
                                            if (AddProjectParameterBinding(projectDocument,
                                                                           projectParameterData,
                                                                           firstWallTypeElement.Category))
                                            {
                                                // Successfully bound
                                                foundParameter = firstWallTypeElement.get_Parameter(projectParameterData.Definition);
                                            }
                                        }
                                        else
                                        {
                                            // The project parameter was already bound to the Walls category.
                                            foundParameter = firstWallTypeElement.get_Parameter(projectParameterData.Definition);
                                        }
                                        if (foundParameter != null)
                                        {
                                            PopulateProjectParameterData(foundParameter, projectParameterData);
                                        }
                                        else
                                        {
                                            // Wouldn't bind to the walls category or wasn't found when already bound
                                            // This should probably never happen?
                                            projectParameterData.IsSharedStatusKnown = false;  // Throw exception?
                                        }
                                    }
                                    else
                                    {
                                        // Found the correct parameter instance on the Project Information object, so use it.
                                        PopulateProjectParameterData(foundParameter, projectParameterData);
                                    }
                                }
                                else
                                {
                                    // The API reports it couldn't bind the parameter to the ProjectInformation category
                                    // This only happens with non-shared Project parameters, which have no GUID anyway
                                    projectParameterData.IsShared = false;
                                    projectParameterData.IsSharedStatusKnown = true;
                                }
                                tempTransaction.RollBack();
                            }
                        }
                        else
                        {
                            // The project parameter was already bound to the Project Information category.
                            foundParameter = projectInfoElement.get_Parameter(projectParameterData.Definition);
                            if (foundParameter != null)
                            {
                                PopulateProjectParameterData(foundParameter, projectParameterData);
                            }
                            else
                            {
                                // This will probably never happen.
                                projectParameterData.IsSharedStatusKnown = false;  // Throw exception?
                            }
                        }
                    }  // Whether or not the Definition object could be found
                }  // For each original project parameter definition
// ================= HELPER METHODS ======================================================================================
        /// 
        /// Returns a list of the objects containing references to the project parameter definitions
        /// 
        /// The project document being quereied
        /// 
        private static List GetProjectParameterData(Document projectDocument)
        {
            // Following good SOA practices, first validate incoming parameters
            if (projectDocument == null)
            {
                throw new ArgumentNullException("projectDocument");
            }
            if (projectDocument.IsFamilyDocument)
            {
                throw new Exception("projectDocument can not be a family document.");
            }
            List result = new List();
            BindingMap map = projectDocument.ParameterBindings;
            DefinitionBindingMapIterator it = map.ForwardIterator();
            it.Reset();
            while (it.MoveNext())
            {
                ProjectParameterData newProjectParameterData = new ProjectParameterData();
                newProjectParameterData.Definition = it.Key;
                newProjectParameterData.Binding = it.Current as ElementBinding;
                result.Add(newProjectParameterData);
            }
            return result;
        }
        /// 
        /// This method takes a category and information about a project parameter and 
        /// adds a binding to the category for the parameter.  It will throw an exception if the parameter
        /// is already bound to the desired category.  It returns whether or not the API reports that it
        /// successfully bound the parameter to the desired category.
        /// 
        /// The project document in which the project parameter has been defined
        /// Information about the project parameter
        /// The additional category to which to bind the project parameter
        /// 
        private static bool AddProjectParameterBinding(Document projectDocument,
                                                       ProjectParameterData projectParameterData,
                                                       Category category)
        {
            // Following good SOA practices, first validate incoming parameters
            if (projectDocument == null)
            {
                throw new ArgumentNullException("projectDocument");
            }
            if (projectDocument.IsFamilyDocument)
            {
                throw new Exception("projectDocument can not be a family document.");
            }
            if (projectParameterData == null)
            {
                throw new ArgumentNullException("projectParameterData");
            }
            if (category == null)
            {
                throw new ArgumentNullException("category");
            }
            bool result = false;
            CategorySet cats = projectParameterData.Binding.Categories;
            if (cats.Contains(category))
            {
                // It's already bound to the desired category.  Nothing to do.
                string errorMessage = string.Format("The project parameter '{0}' is already bound to the '{1}' category.",
                                                    projectParameterData.Definition.Name, 
                                                    category.Name);
                throw new Exception(errorMessage);
            }
            cats.Insert(category);
            // See if the parameter is an instance or type parameter.
            InstanceBinding instanceBinding = projectParameterData.Binding as InstanceBinding;
            if (instanceBinding != null)
            {
                // Is an Instance parameter
                InstanceBinding newInstanceBinding = projectDocument.Application.Create.NewInstanceBinding(cats);
                if (projectDocument.ParameterBindings.ReInsert(projectParameterData.Definition, newInstanceBinding))
                {
                    result = true;
                }
            }
            else
            {
                // Is a type parameter
                TypeBinding typeBinding = projectDocument.Application.Create.NewTypeBinding(cats);
                if (projectDocument.ParameterBindings.ReInsert(projectParameterData.Definition, typeBinding))
                {
                    result = true;
                }
            }
            return result;
        }
        /// 
        /// This method populates the appropriate values on a ProjectParameterData object with information from
        /// the given Parameter object.
        /// 
        /// The Parameter object with source information
        /// The ProjectParameterData object to fill
        private static void PopulateProjectParameterData(Parameter parameter,
                                                         ProjectParameterData projectParameterDataToFill)
        {
            // Following good SOA practices, validat incoming parameters first.
            if (parameter == null)
            {
                throw new ArgumentNullException("parameter");
            }
            if (projectParameterDataToFill == null)
            {
                throw new ArgumentNullException("projectParameterDataToFill");
            }
            projectParameterDataToFill.IsSharedStatusKnown = true;
            projectParameterDataToFill.IsShared = parameter.IsShared;
            if (parameter.IsShared)
            {
                if (parameter.GUID != null)
                {
                    projectParameterDataToFill.GUID = parameter.GUID.ToString();
                }
            }
        }  // end of PopulateProjectParameterData