using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;

namespace GetSetElementTypes
{
   /// <summary>
   /// TODO - Scott Conover 11/12/2013
   /// Quick and dirty modifications made to make the dockable frame show all types, and to make the UI more friendly (sorting the list of types, removing "OST_")
   /// Code could be more efficient and the data structures are not quite as suitable as it once was when this only handled family types.
   /// Also, it would be good to couple this with "PromptForElementTypePlacement()" (perhaps - put a button to the right of each type)
   /// to show how changing the default type also allows streamlining the user's workflow using the API.
   /// </summary>
   public partial class TestGetSetDefaultFamilyTypes : Page, Autodesk.Revit.UI.IDockablePaneProvider
   {
      public static DockablePaneId PaneId = new DockablePaneId(new Guid("{DF0F08C3-447C-4615-B9B9-4843D821012E}"));

      public TestGetSetDefaultFamilyTypes()
      {
         InitializeComponent();

         _handler = new DefaultFamilyTypeCommandHandler();
         _event = Autodesk.Revit.UI.ExternalEvent.Create(_handler);
      }

      public void SetDocument(Document document)
      {
         _document = document;

         _dataGrid_DefaultFamilyTypes.Items.Clear();

         IDictionary<string, int> categories = GetAllFamilyCateogries(_document);

         // This is to hold the results, and sort them before adding to UI
         SortedList<string, Record> records = new SortedList<string, Record>();

         foreach (KeyValuePair<string, int> category in categories)
         {
            Record record = new Record();
            record.CategoryName = category.Key;
            ElementId categoryId = new ElementId(category.Value);

            // TODO  - This could be done more efficiently with ElementType.GetSimilarTypes() - see implementation for non-family types below.
            FilteredElementCollector collector = new FilteredElementCollector(_document);
            collector = collector.OfClass(typeof(FamilySymbol));
            var query = from FamilySymbol et in collector
                        where et.IsValidDefaultFamilyType(categoryId)
                        select et; // Linq query  

            ElementId defaultFamilyTypeId = _document.GetDefaultFamilyTypeId(categoryId);

            List<DefaultFamilyTypeCandidate> defaultFamilyTypeCandidates = new List<DefaultFamilyTypeCandidate>();
            foreach (FamilySymbol t in query)
            {
               DefaultFamilyTypeCandidate item = new DefaultFamilyTypeCandidate()
               {
                  Name = t.FamilyName + " - " + t.Name,
                  Id = t.Id,
                  CategoryId = categoryId
               };
               defaultFamilyTypeCandidates.Add(item);
               if (t.Id.IntegerValue == defaultFamilyTypeId.IntegerValue)
                  record.DefaultFamilyType = item;
            }
            record.DefaultFamilyTypeCandidates = defaultFamilyTypeCandidates;

            records.Add(record.CategoryName, record);
         }
          
        // Non-family types
         IDictionary<string, ElementTypeGroup> nonFamilyTypes = GetElementTypes();

         foreach (KeyValuePair<string, ElementTypeGroup> nonFamilyType in nonFamilyTypes)
         {
             Record record = new Record();
             record.CategoryName = nonFamilyType.Key;
             ElementId defaultId = _document.GetDefaultElementTypeId(nonFamilyType.Value);

             if (defaultId == ElementId.InvalidElementId)
                 continue;

             ElementType eType = _document.GetElement(defaultId) as ElementType;

             ICollection<ElementId> otherTypes = eType.GetSimilarTypes();  // Note - GetSimilarTypes() also returns the type itself

             List<DefaultFamilyTypeCandidate> defaultFamilyTypeCandidates = new List<DefaultFamilyTypeCandidate>();
             foreach (ElementId otherTypeId in otherTypes)
             {
                Element otherTypeElem = _document.GetElement(otherTypeId);
                DefaultFamilyTypeCandidate item = new DefaultFamilyTypeCandidate()
                {
                    TypeGroup = (ElementTypeGroup)Enum.Parse(typeof(ElementTypeGroup), nonFamilyType.Key),
                    Name = otherTypeElem.Name,
                    Id = otherTypeId,
                    CategoryId = ElementId.InvalidElementId
                };
                defaultFamilyTypeCandidates.Add(item);
                if (otherTypeId == defaultId)
                    record.DefaultFamilyType = item;
             }
             if (defaultFamilyTypeCandidates.Count == 0)
                 continue;

             record.DefaultFamilyTypeCandidates = defaultFamilyTypeCandidates;

             records.Add(record.CategoryName, record);
         }


         foreach (Record record in records.Values)
         {
             _dataGrid_DefaultFamilyTypes.Items.Add(record);
         }
      }

      private string GetFamilyName(BuiltInCategory category)
      {
          return category.ToString().Remove(0, 4);
      }

      private IDictionary<string, int> GetAllFamilyCateogries(Document document)
      {
          // Families
         FilteredElementCollector collector = new FilteredElementCollector(document);
         collector = collector.OfClass(typeof(Family));
         var query = collector.ToElements();

         // Sort the categories by "OST_" name
         SortedDictionary<string, int> categoryNamesAndIds = new SortedDictionary<string, int>();

         // The corresponding UI for OST_MatchModel is "Architecture->Build->Component"
         categoryNamesAndIds.Add(GetFamilyName(BuiltInCategory.OST_MatchModel), (int)BuiltInCategory.OST_MatchModel);

         // The corresponding UI for OST_MatchDetail is "Annotate->Detail->Component"
         categoryNamesAndIds.Add(GetFamilyName(BuiltInCategory.OST_MatchDetail), (int)BuiltInCategory.OST_MatchDetail);

         foreach (Family t in query)
         {
             Category category = t.FamilyCategory;
             String categoryName = "";
             int categoryIdInt = category.Id.IntegerValue;
             // Built in
              if (categoryIdInt < 0)
              {
                  categoryName = GetFamilyName((BuiltInCategory)categoryIdInt);
              }
              else
              {
                  categoryName = category.Name;
              }
            if (!categoryNamesAndIds.ContainsKey(categoryName))
               categoryNamesAndIds.Add(categoryName, categoryIdInt);
         }

         return categoryNamesAndIds;
      }

      public IDictionary<string, ElementTypeGroup> GetElementTypes()
      {
          SortedDictionary<string, ElementTypeGroup> types = new SortedDictionary<string, ElementTypeGroup>();

          // Non-family types

          foreach (ElementTypeGroup typeGroup in Enum.GetValues(typeof(ElementTypeGroup)))
          {
              String name = typeGroup.ToString();
              types.Add(name, typeGroup);
          }

          return types;
      }

      #region IDockablePaneProvider Members

      public void SetupDockablePane(Autodesk.Revit.UI.DockablePaneProviderData data)
      {
         data.FrameworkElement = this as FrameworkElement;

         data.InitialState = new Autodesk.Revit.UI.DockablePaneState();
         data.InitialState.DockPosition = Autodesk.Revit.UI.DockPosition.Top;
      }

      #endregion

      private ExternalEvent _event = null;
      private DefaultFamilyTypeCommandHandler _handler;
      private Document _document;

      private void DefaultFamilyTypeSelectionChanged(object sender, SelectionChangedEventArgs e)
      {
         if (e.AddedItems.Count == 1 && e.RemovedItems.Count == 1)
         {
            System.Windows.Controls.ComboBox cb = sender as System.Windows.Controls.ComboBox;
            if (cb == null)
               return;

            DefaultFamilyTypeCandidate item = e.AddedItems[0] as DefaultFamilyTypeCandidate;
            if (item == null)
               return;

            _handler.SetData(item.TypeGroup, item.CategoryId, item.Id);
            _event.Raise();
         }


      }
   }

   public class DefaultFamilyTypeCandidate
   {
       public ElementTypeGroup TypeGroup
       {
           get;
           set;
       }
      
      public String Name
      {
         get;
         set;
      }

      public ElementId Id
      {
         get;
         set;
      }

      public ElementId CategoryId
      {
         get;
         set;
      }

      public override string ToString()
      {
         return Name;
      }
   }

   public class Record
   {
      public String CategoryName
      {
         get;
         set;
      }

      public List<DefaultFamilyTypeCandidate> DefaultFamilyTypeCandidates
      {
         get;
         set;
      }

      public DefaultFamilyTypeCandidate DefaultFamilyType
      {
         get;
         set;
      }
   }


   public class DefaultFamilyTypeCommandHandler : IExternalEventHandler
   {
      ElementTypeGroup _typeGroup;
      ElementId _builtInCategory;
      ElementId _defaultTypeId;
      public void SetData(ElementTypeGroup typeGroup, ElementId categoryId, ElementId typeId)
      {
         _typeGroup = typeGroup;
         _builtInCategory = categoryId;
         _defaultTypeId = typeId;
      }

      public string GetName()
      {
         return "Reset Default family type";
      }


      public void Execute(Autodesk.Revit.UI.UIApplication revitApp)
      {
         using (Transaction tran = new Transaction(revitApp.ActiveUIDocument.Document, "Reset Default family type"))
         {
            tran.Start();
            if (_builtInCategory != ElementId.InvalidElementId)
            {
                revitApp.ActiveUIDocument.Document.SetDefaultFamilyTypeId(_builtInCategory, _defaultTypeId);
            }
            else
            {
                revitApp.ActiveUIDocument.Document.SetDefaultElementTypeId(_typeGroup, _defaultTypeId);
            }
            tran.Commit();
         }
      }

   }  // class CommandHandler

}
