//
// (C) Copyright 2003-2012 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
using System.Data.Linq;
using System.Linq;

using Autodesk.Revit;
using Autodesk.Revit.DB;

using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.ApplicationServices;
using System.Windows.Forms;
using RevitStairs = Autodesk.Revit.DB.Architecture.Stairs;
using System.Diagnostics;
using System.IO;
using System.Drawing;

namespace Revit.Addin.ScheduleImages
{
   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class AddParametersCommand : IExternalCommand
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         DialogResult result = MessageBox.Show("This will add two image parameters to Rebar category via API..." +
            "\r\nMy Image\r\nMy Image(Type)\r\n\r\n Continue?", "Continue?", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
         if (result != DialogResult.OK)
            return Result.Cancelled;

         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         Transaction tran = new Transaction(activeDoc);
         tran.Start("Add Parameters");
         try
         {
            string imageParam = "My Image";
            string imageParamType = "My Image(Type)";
            if (!activeDoc.IsFamilyDocument)
            {
               Utils.CreateSharedParam(activeDoc, BuiltInCategory.OST_Rebar, imageParam, true);
               Utils.CreateSharedParam(activeDoc, BuiltInCategory.OST_Rebar, imageParamType, false);
            }
            else
            {
               // don't add instance shared param as it's not allowed
               //activeDoc.FamilyManager.AddParameter(imageParam, BuiltInParameterGroup.INVALID, ParameterType.Image, true);
               activeDoc.FamilyManager.AddParameter(imageParamType, BuiltInParameterGroup.INVALID, ParameterType.Image, false);
            }
            tran.Commit();
            MessageBox.Show("Succeed to create image parameters.", "Succeed", MessageBoxButtons.OK, MessageBoxIcon.Information);
         }
         catch (System.Exception ex)
         {
            tran.RollBack();
            MessageBox.Show("Sorry, exception occurs -->" + ex.Message);
         }
         return Autodesk.Revit.UI.Result.Succeeded;
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc);
      }
   }

   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class AddImageCommand : IExternalCommand, IExternalCommandAvailability
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         try
         {
            AddImageForm form = new AddImageForm(activeDoc);
            form.ShowDialog();
         }
         catch (System.Exception ex)
         {
            MessageBox.Show("Sorry, exception occurs -->" + ex.Message);
         }
         return Autodesk.Revit.UI.Result.Succeeded;
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc);
      }
   }

   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class ReloadImageCommand : IExternalCommand, IExternalCommandAvailability
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         Utils.ReloadImages(activeDoc);
         return Autodesk.Revit.UI.Result.Succeeded;
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc);
      }
   }

   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class DeleteAllImagesCommand : IExternalCommand, IExternalCommandAvailability
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         FilteredElementCollector coll = new FilteredElementCollector(activeDoc);
         ICollection<ElementId> symIds = coll.OfClass(typeof(ImageType)).ToElementIds();
         Transaction tran = new Transaction(activeDoc);
         tran.Start("Delete Images");
         try
         {
            activeDoc.Delete(symIds);
            tran.Commit();
            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            tran.RollBack();
            MessageBox.Show("Error occurred: " + ex.ToString());
            return Result.Failed;
         }
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc);
      }
   }

   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class SettingsCommand : IExternalCommand
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         try
         {
            SettingForm settingForm = new SettingForm(activeDoc);
            settingForm.ShowDialog();
         }
         catch (System.Exception ex)
         {
            MessageBox.Show("Sorry, exception occurs -->" + ex.Message);
         }
         return Autodesk.Revit.UI.Result.Succeeded;
      }
   }

   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class ScheduleThumbnailCommand : IExternalCommand, IExternalCommandAvailability
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         DialogResult result = MessageBox.Show("This will update furnitures with its type's thumbnail... \r\n\r\n Continue?", 
            "Continue?", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
         if (result != DialogResult.OK)
            return Result.Cancelled;

         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         FilteredElementCollector coll = new FilteredElementCollector(activeDoc);
         List<ElementType> eTypes = coll.OfCategory(BuiltInCategory.OST_Furniture)
            .WhereElementIsElementType().Cast<ElementType>().ToList();

         Dictionary<ElementId, ElementId> typeSymDict = new Dictionary<ElementId, ElementId>();
         Transaction tran = new Transaction(activeDoc);
         tran.Start("Thumbnail Update");
         try
         {
            /////////////////////////////////////////////////////////
            // export image
            foreach (ElementType shape in eTypes)
            {
               Bitmap bp = shape.GetPreviewImage(new Size(96, 96));
               if (null != bp)
               {
                  try
                  {
                     string imageName = Path.Combine(AppConfig.AddonDirectory, @"\Caches\" + makeFileName(shape.Name) + ".bmp");
                     FileInfo fi = new FileInfo(imageName);
                     if (!Directory.Exists(fi.DirectoryName))
                        Directory.CreateDirectory(fi.DirectoryName);
                     if (File.Exists(imageName))
                        File.Delete(imageName);
                     //
                     // save it
                     bp.Save(imageName);
                     //
                     // now import it
                     ImageType newSym = ImageType.Create(activeDoc, imageName);
                     typeSymDict.Add(shape.Id, newSym.Id);
                  }
                  catch (Exception ex)
                  {
                     Trace.WriteLine("Failed to export: " + shape.Name + ", ex: " + ex.ToString());
                  }
               }
            }
            //
            ////////////////////////////////////////////////////
            // Update elements images 
            coll = new FilteredElementCollector(activeDoc);
            List<Element> furnitures = coll.OfCategory(BuiltInCategory.OST_Furniture)
               .WhereElementIsNotElementType().ToList();
            foreach (Element elem in furnitures)
            {
               if(!typeSymDict.ContainsKey(elem.GetTypeId()))
                  continue;

               Parameter imgParam = elem.get_Parameter(BuiltInParameter.ALL_MODEL_IMAGE);
               if (null == imgParam || imgParam.IsReadOnly || imgParam.StorageType != StorageType.ElementId)
                  continue;
               ElementId symId = typeSymDict[elem.GetTypeId()];
               imgParam.Set(symId);
            }
            tran.Commit();
            MessageBox.Show("Succeed to update furnitures thumbnail images!", "Succeed", MessageBoxButtons.OK, MessageBoxIcon.Information);
         }
         catch (Exception ex)
         {
            tran.RollBack();
            MessageBox.Show(ex.ToString());
         }
         return Autodesk.Revit.UI.Result.Succeeded;
      }

      private string makeFileName(string name)
      {
         string invalid = "\\/:*?\"<>|";
         foreach (char ch in invalid.ToCharArray())
         {
            name = name.Replace(ch.ToString(), "");
         }
         return name;
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc && !rvtDoc.IsFamilyDocument);
      }
   }

   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class RestoreSchedulesCommand : IExternalCommand, IExternalCommandAvailability
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         FilteredElementCollector coll = new FilteredElementCollector(activeDoc);
         List<ViewSchedule> vSchedules = coll.OfClass(typeof(ViewSchedule)).Cast<ViewSchedule>().ToList();
         Transaction tran = new Transaction(activeDoc);
         tran.Start("Restore Image Size");
         try
         {
            foreach (ViewSchedule vSchedule in vSchedules)
            {
               vSchedule.RestoreImageSize();
            }
            tran.Commit();
         }
         catch (Exception ex)
         {
            tran.RollBack();
            MessageBox.Show(ex.ToString());
         }
         return Autodesk.Revit.UI.Result.Succeeded;
      }

      private string makeFileName(string name)
      {
         string invalid = "\\/:*?\"<>|";
         foreach (char ch in invalid.ToCharArray())
         {
            name = name.Replace(ch.ToString(), "");
         }
         return name;
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc && !rvtDoc.IsFamilyDocument);
      }
   }

   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class AddImageRandomCommand : IExternalCommand, IExternalCommandAvailability
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         try
         {
            MainForm form = new MainForm(activeDoc);
            form.ShowDialog();
         }
         catch (System.Exception ex)
         {
            MessageBox.Show("Sorry, exception occurs -->" + ex.Message);
         }
         return Autodesk.Revit.UI.Result.Succeeded;
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc);
      }
   }
   
   /// <summary>
   /// Demonstrate how a basic ExternalCommand can be added to the Revit user interface. 
   /// And demonstrate how to create a Revit style dialog.
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
   public class AssignImagesCommand : IExternalCommand, IExternalCommandAvailability
   {
      public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
          ref string message, Autodesk.Revit.DB.ElementSet elements)
      {
         // NOTES: Anything can be done in this method, such as create a message box, 
         // a task dialog or fetch some information from revit and so on.
         // We mainly use the task dialog for example.

         // Get the application and document from external command data.
         Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application;
         Document activeDoc = commandData.Application.ActiveUIDocument.Document;
         try
         {
            Transaction tran = new Transaction(activeDoc);
            tran.Start("Update walls");
            //
            // images
            FilteredElementCollector coll = new FilteredElementCollector(activeDoc);
            List<ElementId> imageSymbols = new List<ElementId>();
            imageSymbols.AddRange(coll.OfClass(typeof(ImageType)).ToElementIds());
            Random random = new Random(imageSymbols.Count);
            //
            // walls(instance + type) + Door instance.         
            List<Element> allElems = new List<Element>();
            FilteredElementCollector wallColl = new FilteredElementCollector(activeDoc);
            wallColl.OfCategory(BuiltInCategory.OST_Walls);
            allElems.AddRange(wallColl.ToElements());
            FilteredElementCollector doorColl = new FilteredElementCollector(activeDoc);
            doorColl.OfCategory(BuiltInCategory.OST_Doors).WhereElementIsNotElementType();
            allElems.AddRange(doorColl.ToElements());
            //ICollection<Element> elems = wallColl.UnionWith(doorColl).ToElements();
            //
            int index = 0;
            ProgressForm progress = new ProgressForm(AppConfig.AppName, "Start update image params...", allElems.Count);
            progress.StartPosition = FormStartPosition.CenterScreen;
            progress.Show(null);
            foreach (Element elem in allElems)
            {
               progress.performProgressBarSteps(string.Format("Update element {0}...{1}/{2}", elem.Id.IntegerValue, index++, allElems.Count));

               Parameter imageParam = (elem is ElementType) ? 
                   elem.get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_IMAGE) : elem.get_Parameter(BuiltInParameter.ALL_MODEL_IMAGE);
               if (imageParam != null && !imageParam.IsReadOnly && imageParam.StorageType == StorageType.ElementId)
               {
                  int idx = random.Next(0, imageSymbols.Count - 1);
                  imageParam.Set(imageSymbols[idx]);
               }
            }
            progress.Close();
            tran.Commit();
         }
         catch (System.Exception ex)
         {
            MessageBox.Show("Sorry, exception occurs -->" + ex.ToString());
         }
         return Autodesk.Revit.UI.Result.Succeeded;
      }

      public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
      {
         Document rvtDoc = applicationData.ActiveUIDocument.Document;
         return (null != rvtDoc);
      }
   }
}
