using System;
using System.Collections;
using System.Collections.Generic;  // Zawiera klasę List<T>
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

/// <remarks>
///    File Copier - Program demonstrujący formularze Windows
///    (c) Copyright 2005 Liberty Associates, Inc.
/// </remarks>
namespace FileCopier
{


	/// <summary>
	/// Formularz demonstrujący zastosowanie formularzy Windows
	/// </summary>
	partial class frmFileCopier : Form
	{
		private const int MaxLevel = 2;
		public frmFileCopier()
		{
			InitializeComponent();
			FillDirectoryTree(tvwSource, true);
			FillDirectoryTree(tvwTarget, false);
		}

		/// <summary>
		///    Zagnieżdżona klasa odpowiedzialna za porównywanie 
		///    dwóch plików, które mają być sortowane od dużych do małych, 
		///    dlatego trzeba zamienić zwykle zwracane wartości.
		/// </summary>
		public class FileComparer : IComparer<FileInfo>
		{
			public int Compare(FileInfo file1, FileInfo file2)
			{
				if (file1.Length > file2.Length)
				{
					return -1;
				}
				if (file1.Length < file2.Length)
				{
					return 1;
				}
				return 0;
			}

			public bool Equals(FileInfo x, FileInfo y)
			{
				throw new NotImplementedException();
			}

			public int GetHashCode(FileInfo x)
			{
				throw new NotImplementedException();
			}

		}

		/// <summary>
		/// Wspólna metoda obu drzew katalogów
		/// </summary>
		/// <param name="tvw">którą kontrolkę TreeView obsługuje metoda</param>
		/// <param name="isSource">pobiera pliki, jeśli true</param>
		private void FillDirectoryTree(TreeView tvw, bool isSource)
		{
			//  Zapełnia tvwSource, źródłową kontrolkę TreeView, 
			//  zawartością
			//  lokalnego dysku twardego.
			//  Najpierw należy usunąć wszystkie węzły.
			tvw.Nodes.Clear();

			//  Pobiera napędy logiczne i umieszcza je 
			//  w węzłach głównych. Zapełnia tablicę 
			//  wszystkimi napędami logicznymi maszyny.
			string[] strDrives = Environment.GetLogicalDrives();

			//  Pobiera kolejne napędy i dodaje je do drzewa.
			//  Używa bloku try-catch, dlatego jeśli napęd nie jest gotowy, 
			//  na przykład jest to pusta stacja dyskietek lub napęd CD,
			//  metoda nie doda go do drzewa.
			foreach (string rootDirectoryName in strDrives)
			{
				try
				{
					//  Zapełnia tablicę wszystkimi podkatalogami
					//  pierwszego poziomu. Jeśli napęd nie jest gotowy,
					//  ta operacja zgłasza wyjątek.
					DirectoryInfo dir =
						new DirectoryInfo(rootDirectoryName);

					dir.GetDirectories();      // Wymusza wyjątek, jeśli napęd nie jest gotowy

					TreeNode ndRoot = new TreeNode(rootDirectoryName);

					//  Dodaje węzły reprezentujące wszystkie katalogi główne.
					tvw.Nodes.Add(ndRoot);

					//  Dodaje węzły podkatalogów.
					//  Jeśli jest to źródłowa kontrolka TreeView, 
					//  należy pobrać także nazwy plików.
					if (isSource)
					{
						GetSubDirectoryNodes(
						  ndRoot, ndRoot.Text, true, 1);
					}
					else
					{
						GetSubDirectoryNodes(
						  ndRoot, ndRoot.Text, false, 1);
					}
				}
				// Przechwytuje wszystkie błędy, 
				// na przykład niegotowy napęd.
				catch
				{
				}
				Application.DoEvents();
			} // Koniec foreach rootDirectoryName
		} //  Koniec FillSourceDirectoryTree

		/// <summary>
		/// Pobiera wszystkie podkatalogi 
		/// przekazanego węzła katalogu.
		/// Dodaje je do drzewa katalogu.
		/// Przekazywane parametry to węzeł nadrzędny
		/// danego podkatalogu,
		/// pełna ścieżka dostępu do tego podkatalogu 
		/// i wartość logiczna informująca, czy
		/// należy pobierać pliki tego podkatalogu, czy nie.
		/// </summary>
		private void GetSubDirectoryNodes(
			TreeNode parentNode, string fullName, bool getFileNames, int level)
		{
			DirectoryInfo dir = new DirectoryInfo(fullName);
			DirectoryInfo[] dirSubs = dir.GetDirectories();

			//  Dodaje węzeł potomny dla każdego podkatalogu.
			foreach (DirectoryInfo dirSub in dirSubs)
			{
				// Nie należy wyświetlać ukrytych katalogów
				if ((dirSub.Attributes & FileAttributes.Hidden)
				   != 0)
				{
					continue;
				}

				/// <summary>
				///    Każdy katalog ma pełną ścieżkę dostępu.
				///    Trzeba rozdzielić ją według lewych ukośników, 
				///    i użyć jedynie
				///    ostatniego węzła drzewa.
				///    Trzeba użyć podwójnych lewych ukośników,
				///    ponieważ zwykle lewy
				///    ukośnik to znak ucieczki
				/// </summary>
				TreeNode subNode = new TreeNode(dirSub.Name);
				parentNode.Nodes.Add(subNode);

				//  Rekurencyjnie wywołuje metodę GetSubDirectoryNodes.
				if (level < MaxLevel)
				{
					GetSubDirectoryNodes(
					  subNode, dirSub.FullName, getFileNames, level + 1);
				}
			} // Koniec foreach DirectoryInfo in dirSubs

			if (getFileNames)
			{
				//  Pobiera wszystkie pliki danego węzła.
				FileInfo[] files = dir.GetFiles();

				// Po dodaniu węzłów 
				// należy dodać pliki danego podkatalogu.
				foreach (FileInfo file in files)
				{
					TreeNode fileNode = new TreeNode(file.Name);
					parentNode.Nodes.Add(fileNode);
				} // Koniec foreach FileInfo in files
			}    // Koniec if getFileNames
		}       // Koniec GetSubDirectoryNodes


		/// <summary>
		///    Tworzy posortowaną listę wszystkich
		///    wybranych plików i kopiuje
		///    je do docelowego katalogu
		/// </summary>
		private void btnCopy_Click(object sender,
			System.EventArgs e)
		{
			// Pobiera listę
			List<FileInfo> fileList = GetFileList();

			// Kopiuje pliki
			foreach (FileInfo file in fileList)
			{
				try
				{
					// Aktualizuje etykietę, aby wyświetlić informacje o postępie
					lblStatus.Text = "Kopiowanie " + txtTargetDir.Text +
						"\\" + file.Name + "...";
					Application.DoEvents();

					// Kopiuje plik do docelowego katalogu
					file.CopyTo(txtTargetDir.Text + "\\" +
						file.Name, chkOverwrite.Checked);
				}

				catch (Exception ex)
				{
					// Można wykonać inne operacje  
					// oprócz wyświetlenia komunikatu
					MessageBox.Show(ex.Message);
				}
			}  // Koniec foreach FileInfo in fileList
			lblStatus.Text = "Gotowe.";
		}

		/// <summary>
		///    Informuje węzeł nadrzędny każdego drzewa o konieczności
		///    usunięcia zaznaczenia wszystkich podrzędnych węzłów
		/// </summary>
		private void btnClear_Click(object sender, System.EventArgs e)
		{
			// Pobiera główny węzeł każdego napędu i
			// nakazuje rekurencyjnie usunąć zaznaczenie
			foreach (TreeNode node in tvwSource.Nodes)
			{
				SetCheck(node, false);
			}
		}

		/// <summary>
		///    Po kliknięciu przycisku Anuluj, program kończy działanie
		/// </summary>
		private void btnCancel_Click(object sender, EventArgs e)
		{
			Application.Exit();
		}

		/// <summary>
		///    Przyjmuje węzeł i listę ArrayList, 
		///    a następnie zapełnia listę nazwami 
		///    wszystkich zaznaczonych plików
		/// </summary>
		// Zapełnia listę ArrayList pełnymi ścieżkami do
		// wszystkich zaznaczonych plików
		private void GetCheckedFiles(TreeNode node,
			List<string> fileNames)
		{
			// Jeśli jest to liść...
			if (node.Nodes.Count == 0)
			{
				// Jeśli węzeł jest zaznaczony...
				if (node.Checked)
				{
					// Należy pobrać pełną ścieżkę i dodać ją do listy ArrayList
					string fullPath = GetParentString(node);
					fileNames.Add(fullPath);
				}
			}
			else  // Jeśli dany węzeł nie jest liściem
			{
				// Jeśli dany węzeł nie jest liściem
				foreach (TreeNode n in node.Nodes)
				{
					GetCheckedFiles(n, fileNames);
				}
			}
		}

		/// <summary>
		///    Przyjmuje węzeł i zwraca
		///    pełną ścieżkę
		/// </summary>
		private string GetParentString(TreeNode node)
		{
			// Jeśli jest to węzeł nadrzędny (c:\), metoda zwraca jego właściwość Text
			if (node.Parent == null)
			{
				return node.Text;
			}
			else
			{
				// Rekurencyjnie przechodzi w górę ścieżki, a następnie 
				// dodaje dany węzeł i ukośnik.
				// Jeśli węzeł to liść, kod nie dodaje ukośnika
				return GetParentString(node.Parent) + node.Text +
					(node.Nodes.Count == 0 ? "" : "\\");
			}
		}

		/// <summary>
		///    Metoda współdzielona przez przyciski Usuń i Kopiuj.
		///    Tworzy posortowaną listę wszystkich
		///    zaznaczonych plików
		/// </summary>
		private List<FileInfo> GetFileList()
		{
			// Tworzy nieposortowaną listę pełnych nazw plików
			List<string> fileNames = new List<string>();

			// Zapełnia listę fileNames pełną 
			// ścieżką do każdego kopiowanego pliku
			foreach (TreeNode theNode in tvwSource.Nodes)
			{
				GetCheckedFiles(theNode, fileNames);
			}

			// Tworzy listę przechowującą obiekty FileInfo
			List<FileInfo> fileList = new List<FileInfo>();

			// Każdą nazwę pliku z nieposortowanej listy,
			// jeśli odpowiada ona plikowi (a nie katalogowi),
			// należy dodać do listy plików
			foreach (string fileName in fileNames)
			{
				// Tworzy obiekt file na podstawie nazwy
				FileInfo file = new FileInfo(fileName);

				// Sprawdza, czy dany plik istnieje na dysku.
				// Jeśli jest to katalog, operacja kończy się niepowodzeniem
				if (file.Exists)
				{
					// Zarówno klucz jak i wartość to pliki.
					// Czy nie łatwiej byłoby użyć pustych wartości?
					fileList.Add(file);
				} // Koniec if file.Exists
			}    // Koniec foreach fileName in fileNames

			// Tworzy egzemplarz interfejsu IComparer
			IComparer<FileInfo> comparer = (IComparer<FileInfo>)new FileComparer();

			// Przekazuje obiekt porównujący do metody Sort(), dzięki czemu lista
			// zostanie posortowana przy użyciu metody Compare tego obiektu.
			fileList.Sort(comparer);
			return fileList;
		}

		/// <summary>
		///    Sprawdza, czy użytkownik chce usunąć katalog.
		///    Tworzy listę i usuwa kolejne elementy
		/// </summary>
		private void btnDelete_Click(object sender, System.EventArgs e)
		{
			// Pyta użytkownika, czy jest pewny
			System.Windows.Forms.DialogResult result =
			   MessageBox.Show(
			   "Czy na pewno?",                    // Komunikat
			   "Usuwanie plików",                  // Nagłówek
			   MessageBoxButtons.OKCancel,         // Przyciski
			   MessageBoxIcon.Exclamation,         // Ikony
			   MessageBoxDefaultButton.Button2);  // Przycisk domyślny

			// Jeśli użytkownik jest pewien...
			if (result == System.Windows.Forms.DialogResult.OK)
			{
				// Przechodzi przez listę i usuwa elementy.
				// Pobiera listę zaznaczonych plików
				List<FileInfo> fileNames = GetFileList();

				foreach (FileInfo file in fileNames)
				{
					try
					{
						// Aktualizuje etykietę, aby wyświetlić informacje o postępie
						lblStatus.Text = "Usuwanie " +
							txtTargetDir.Text + "\\" +
							file.Name + "...";
						Application.DoEvents();

						// Uwaga, niebezpieczeństwo!
						file.Delete();
					}
					catch (Exception ex)
					{
						// Można wykonać dodatkowe operacje 
						// oprócz wyświetlenia komunikatu
						MessageBox.Show(ex.Message);
					} // Koniec catch
				}    // Koniec foreach FileInfo in fileNames
				lblStatus.Text = "Gotowe.";
				Application.DoEvents();
			} // Koniec if result = OK
		}    // Koniec btnDelete_Click

		/// <summary>
		///    Pobiera pełną ścieżkę do wybranego katalogu i
		///    wyświetla ją w kontrolce txtTargetDir
		/// </summary>
		private void tvwTargetDir_AfterSelect(
			object sender,
			System.Windows.Forms.TreeViewEventArgs e)
		{
			// Pobiera pełną ścieżkę do wybranego katalogu
			string theFullPath = GetParentString(e.Node);

			// Jeśli nie jest to liść, ścieżka kończy się lewym ukośnikiem.
			// Należy usunąć go
			if (theFullPath.EndsWith("\\"))
			{
				theFullPath =
					theFullPath.Substring(0, theFullPath.Length - 1);
			}
			// Wyświetla ścieżkę w polu tekstowym
			txtTargetDir.Text = theFullPath;
		}

		/// <summary>
		///    Zaznacza każdy węzeł poniżej bieżącego
		///    na podstawie wartości właściwości checked
		/// </summary>
		private void tvwSource_AfterCheck(object sender,
			System.Windows.Forms.TreeViewEventArgs e)
		{
			// Wywołuje rekurencyjną metodę.
			// e.node to węzeł zaznaczony przez użytkownika.
			// Stan zaznaczenia jest już zmieniony, 
			// zanim program do niego dotrze.
			// Dlatego trzeba przekazać stan
			// o wartości e.node.Checked.
			SetCheck(e.Node, e.Node.Checked);
		}

		/// <summary>
		///    Rekurencyjnie dodaje lub usuwa zaznaczenie
		/// </summary> 
		private void SetCheck(TreeNode node, bool check)
		{
			// Wyszukuje wszystkie węzły potomne bieżącego węzła
			foreach (TreeNode n in node.Nodes)
			{
				n.Checked = check;   // Zaznacza węzeł

				// Jeśli jest to węzeł drzewa, metoda rekurencyjnie przechodzi dalej
				if (n.Nodes.Count != 0)
				{
					SetCheck(n, check);
				} // Koniec if Nodes.Count != 0
			}    // Koniec foreach TreeNode in Nodes
		}       // Koniec SetCheck

		/// <summary>
		/// Wspólna metoda obsługi zdarzenia beforeExpand
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void tvwExpand(object sender, TreeViewCancelEventArgs e)
		{
			TreeView tvw = (TreeView)sender;
			bool getFiles = tvw == tvwSource;
			TreeNode currentNode = e.Node;
			string fullName = currentNode.FullPath;
			currentNode.Nodes.Clear();
			GetSubDirectoryNodes(currentNode, fullName, getFiles, 1);
		}  // Koniec tvwExpand
	}     // Koniec frmFileCopier
}        // Koniec FileCopier
