context: //
  // Gendarme.cs: A SWF-based Wizard Runner for Gendarme
  //
  // Authors:
  //	Sebastien Pouliot <sebastien@ximian.com>
  //
  // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Collections.Specialized;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Windows.Forms;
  
  using Gendarme.Framework;
  using Gendarme.Properties;
  
  using Mono.Cecil;
  
  namespace Gendarme {
  
  	public partial class Wizard : Form {
  
  		// This is used for methods which we execute within a thread, but we don't
  		// execute the methods concurrently so we can use single thread.
  		[ThreadModel (ThreadModel.SingleThread)]
  		sealed class AssemblyInfo {
  			private DateTime timestamp;
  			private AssemblyDefinition definition;
  
  			public DateTime Timestamp {
  				get { return timestamp; }
  				set { timestamp = value; }
  			}
  
  			public AssemblyDefinition Definition {
  				get { return definition; }
  				set { definition = value; }
  			}
  		}
  
  		private const string BaseUrl = "http://www.mono-project.com/";
  		private const string DefaultUrl = BaseUrl + "Gendarme";
  		private const string BugzillaUrl = "http://bugzilla.novell.com";
  
  		static Process process;
  
  		private bool rules_populated;
  		private Dictionary<string, AssemblyInfo> assemblies;
  		private GuiRunner runner;
  		private int counter;
  
  		private Action assembly_loader;
  		private IAsyncResult assemblies_loading;
  		private object loader_lock = new object ();
  
  		private Action rule_loader;
  		private IAsyncResult rules_loading;
  
  		private Action analyze;
  
  		private string html_report_filename;
  		private string xml_report_filename;
  		private string text_report_filename;
  
  
  		public Wizard ()
  		{
  			InitializeComponent ();
  			// hide the tabs from the TabControl/TabPage[s] being [mis-]used
  			// to implement this wizard
  			wizard_tab_control.Top = -24;
  
  			welcome_link_label.Text = DefaultUrl;
  
  			Version v = typeof (IRule).Assembly.GetName ().Version;
  			if ((v.Major == 0) && (v.Minor == 0))
  				welcome_gendarme_label.Text = "Gendarme (development snapshot)";
  			else
  				welcome_gendarme_label.Text = String.Format ("Gendarme, version {0}", v);
  
  			assembly_loader = UpdateAssemblies;
  
  			UpdatePageUI ();
  		}
  
  		[ThreadModel (ThreadModel.SingleThread)]
  		static void EndCallback (IAsyncResult result)
  		{
  			(result.AsyncState as Action).EndInvoke (result);
  		}
  
  		static void Open (string filename)
  		{
  			if (process == null)
  				process = new Process ();
  			process.StartInfo.Verb = "open";
  			process.StartInfo.FileName = filename;
  			process.Start ();
  		}
  
  		#region general wizard code
  
  		public Page Current {
  			get { return (Page) wizard_tab_control.SelectedIndex; }
  			set {
  				wizard_tab_control.SelectedIndex = (int) value;
  				UpdatePageUI ();
  			}
  		}
  
  		public GuiRunner Runner {
  			get {
  				if (runner == null)
  					runner = new GuiRunner (this);
  				return runner;
  			}
  		}
  
  		private void BackButtonClick (object sender, EventArgs e)
  		{
  			switch (Current) {
  			case Page.Welcome:
  				return;
  			case Page.AddFiles:
  				Current = Page.Welcome;
  				break;
  			case Page.SelectRules:
  				Current = Page.AddFiles;
  				break;
  			case Page.Options:
  				Current = Page.SelectRules;
  				break;
  			case Page.Analyze:
  				// then ask confirmation before aborting 
  				// and move back one step
  				if (ConfirmAnalyzeAbort (false))
  					Current = Page.Options;
  				break;
  			case Page.Report:
  				// move two step back (i.e. skip analyze)
  				Current = Page.Options;
  				break;
  			}
  		}
  
  		private void NextButtonClick (object sender, EventArgs e)
  		{
  			switch (Current) {
  			case Page.Welcome:
  				Current = Page.AddFiles;
  				break;
  			case Page.AddFiles:
  				Current = Page.SelectRules;
  				break;
  			case Page.SelectRules:
  				Current = Page.Options;
  				break;
  			case Page.Options:
  				UpdateOptions ();
  				Current = Page.Analyze;
  				break;
  			case Page.Analyze:
  			case Page.Report:
  				// should not happen
  				return;
  			}
  		}
  
  		private void CancelButtonClick (object sender, EventArgs e)
  		{
  			// if we're analyzing...
  			if (Current == Page.Analyze) {
  				// then ask confirmation before aborting
  				if (!ConfirmAnalyzeAbort (true))
  					return;
  			}
  			Close ();
  		}
  
  		private void HelpButtonClick (object sender, EventArgs e)
  		{
  			// open web browser to http://www.mono-project.com/Gendarme
  			Open (DefaultUrl);
  		}
  
  		private void UpdatePageUI ()
  		{
  			back_button.Enabled = true;
  			next_button.Enabled = true;
  			cancel_button.Text = "Cancel";
  
  			switch (Current) {
  			case Page.Welcome:
  				UpdateWelcomeUI ();
  				break;
  			case Page.AddFiles:
  				UpdateAddFilesUI ();
  				break;
  			case Page.SelectRules:
  				UpdateSelectRulesUI ();
  				break;
  			case Page.Options:
  				UpdateOptionsUI ();
  				break;
  			case Page.Analyze:
  				UpdateAnalyzeUI ();
  				break;
  			case Page.Report:
  				UpdateReportUI ();
  				break;
  			}
  		}
  
  		#endregion
  
  		#region Welcome
  
  		private void UpdateWelcomeUI ()
  		{
  			back_button.Enabled = false;
  			if (rule_loader == null) {
  				rule_loader = Runner.LoadRules;
  				rules_loading = rule_loader.BeginInvoke (EndCallback, rule_loader);
  			}
  		}
  
  		private void GendarmeLinkClick (object sender, LinkLabelLinkClickedEventArgs e)
  		{
  			Open (DefaultUrl);
  		}
  
  		#endregion
  
  		#region Add Files
  
  		private void UpdateAddFilesUI ()
  		{
  			if (assemblies == null)
  				assemblies = new Dictionary<string, AssemblyInfo> ();
  
  			int files_count = file_list_box.Items.Count;
  			bool has_files = (files_count > 0);
  			next_button.Enabled = has_files;
  			remove_file_button.Enabled = has_files;
  			if (has_files) {
  				add_files_count_label.Text = String.Format ("{0} assembl{1} selected",
  					files_count, files_count == 1 ? "y" : "ies");
  			} else {
  				add_files_count_label.Text = "No assembly selected.";
  			}
  		}
  
  		private void AddFilesButtonClick (object sender, EventArgs e)
  		{
  			if (open_file_dialog.ShowDialog (this) == DialogResult.OK) {
  				foreach (string filename in open_file_dialog.FileNames) {
  					// don't add duplicates
  					if (!file_list_box.Items.Contains (filename)) {
  						file_list_box.Items.Add (filename);
  						assemblies.Add (filename, new AssemblyInfo ());
  					}
  				}
  			}
  			UpdatePageUI ();
  		}
  
  		private void RemoveFileButtonClick (object sender, EventArgs e)
  		{
  			// remove from the last one to the first one 
  			// so the indices are still valid during the removal operation
  			for (int i = file_list_box.SelectedIndices.Count - 1; i >= 0; i--) {
  				int remove = file_list_box.SelectedIndices [i];
  
  				// if some AssemblyDefinition are already loaded...
  				if (assemblies != null) {
  					// then look if we need to remove them too!
  					assemblies.Remove ((string) file_list_box.Items [remove]);
  				}
  				file_list_box.Items.RemoveAt (remove);
  			}
  			UpdatePageUI ();
  		}
  
  		public void UpdateAssemblies ()
  		{
  			if (IsDisposed)
  				throw new ObjectDisposedException (GetType ().Name);
  
  			// do not let the final check be done while we're still loading assemblies
  			lock (loader_lock) {
  				foreach (KeyValuePair<string,AssemblyInfo> kvp in assemblies) {
  					DateTime last_write = File.GetLastWriteTimeUtc (kvp.Key);
  					if ((kvp.Value.Definition == null) && (kvp.Value.Timestamp < last_write)) {
  						AssemblyInfo a = kvp.Value;
  						string filename = kvp.Key;
  						try {
  							a.Definition = AssemblyDefinition.ReadAssembly (filename, new ReaderParameters { AssemblyResolver = AssemblyResolver.Resolver });
  						}
  						catch (BadImageFormatException) {
  							// continue loading & analyzing assemblies
  							Runner.Warn ("Invalid image: " + filename);
  						}
  						catch (FileNotFoundException fnfe) {
  							// e.g. .netmodules
  							// continue loading & analyzing assemblies
  							Runner.Warn (fnfe.Message + " while loading " + filename);
  						}
  						finally {
  							a.Timestamp = last_write;
  						}
  					}
  				}
  			}
  		}
  
  		#endregion
  
  		#region Select Rules
  
  		private void UpdateSelectRulesUI ()
  		{
  			// asynchronously load assemblies (or the one that changed)
  			assemblies_loading = assembly_loader.BeginInvoke (EndCallback, assembly_loader);
  
  			rules_count_label.Text = String.Format ("{0} rules are available.", Runner.Rules.Count);
  			if (rules_loading == null)
  				throw new InvalidOperationException ("rules_loading");
  
  			bool completed = rules_loading.IsCompleted;
  			next_button.Enabled = completed;
  			rules_tree_view.Enabled = completed;
  			rules_loading.AsyncWaitHandle.WaitOne ();
  			PopulateRules ();
  		}
  
  		private void PopulateRules ()
  		{
  			if (rules_populated)
  				return;
  
  			// if settings are empty (like default) then all rules are active
  			StringCollection rules = Settings.Default.Rules;
  			bool all_rules = ((rules == null) || (rules.Count == 0));
  
  			Dictionary<string, TreeNode> nodes = new Dictionary<string, TreeNode> ();
  
  			rules_tree_view.BeginUpdate ();
  			rules_tree_view.AfterCheck -= RulesTreeViewAfterCheck;
  			foreach (IRule rule in Runner.Rules) {
  				TreeNode parent;
  				string full_name = rule.FullName;
  				string name = rule.Name;
  				string name_space = full_name.Substring (0, full_name.Length - name.Length - 1);
  				if (!nodes.TryGetValue (name_space, out parent)) {
  					parent = new TreeNode (name_space);
  					parent.Checked = all_rules;
  					nodes.Add (name_space, parent);
  					rules_tree_view.Nodes.Add (parent);
  				}
  
  				TreeNode node = new TreeNode (name);
  
gt: 				node.Checked = all_rules || rules.Contains (full_name);