# encoding: ascii
# api: powershell
# title: Start-FolderSync
# description: Small script to strongly syncronyze folder with ‘original’.
# version: 0.1
# type: class
# author: M Skourlatov
# license: CC0
# function: Start-FolderSync
# x-poshcode-id: 6890
# x-archived: 2017-05-13T18:16:33
# x-published: 2017-05-09T23:46:00
#
#
Add-Type -TypeDefinition @"

public struct HashContainer : System.IComparable
{
	public ulong High;
	public ulong Low;

	public HashContainer(byte[] Buffer)
	{
		System.Array.Reverse(Buffer);
		this.Low = System.BitConverter.ToUInt64(Buffer, 0);
		this.High = System.BitConverter.ToUInt64(Buffer, 8);
	}

	public int CompareTo(object obj)
	{
		if (obj == null) { return 1; }
		var other = (HashContainer)obj;

		int compare = this.High.CompareTo(other.High);
		if (compare != 0) return compare;
		return this.Low.CompareTo(other.Low);
	}

	public override string ToString()
	{
		return string.Format("{0:X}{1:X}", this.High, this.Low);
	}

}

public class FIComparable : System.IComparable
{
	public System.IO.FileInfo Info;

	private bool hasHash;
	private HashContainer hash;

	public FIComparable(System.IO.FileInfo info)
	{
		this.Info = info;
		this.hasHash = false;
	}

	public HashContainer GetHash()
	{
		if (this.hasHash) { return this.hash; }

		var crypto = new System.Security.Cryptography.MD5CryptoServiceProvider();
		var stream = new System.IO.FileStream(
			this.Info.FullName,
			System.IO.FileMode.Open,
			System.IO.FileAccess.Read,
			System.IO.FileShare.None);
		var buf = crypto.ComputeHash(stream);
		stream.Close();

		this.hash = new HashContainer(buf);
		this.hasHash = true;
		return this.hash;
	}

	public int CompareTo(object obj)
	{
		if (obj == null) { return 1; }
		var other = obj as FIComparable;

		if (null != other)
		{
			int compare = this.Info.Name.CompareTo(other.Info.Name);
			if (compare != 0) { return compare; }
			compare = this.Info.Length.CompareTo(other.Info.Length);
			if (compare != 0) { return compare; }
			return this.GetHash().CompareTo(other.GetHash());
		}
		else { throw new System.ArgumentException(); }
	}
	
	public override string ToString() { return this.Info.Name; }
}
"@

Function Start-FolderSync
{
	Param
	(
		[parameter(Mandatory, Position = 0)]
		[ValidateScript({ Test-Path -Path $_ -PathType 'Container' })]
		[string]$Reference,
		[parameter(Mandatory, Position = 1)]
		[string]$Synchronized
	)

	function Get-Comparable($list)
	{
		$sync = New-Object 'System.Collections.Generic.List[FIComparable]'
		foreach ($item in $list) { $sync.Add((New-Object FIComparable $item)) }
		return $sync
	}

	$gotcha = Get-ChildItem -LiteralPath $Reference
	$fileList = New-Object 'System.Collections.Generic.List[System.IO.FileInfo]'
	$copyList = New-Object 'System.Collections.Generic.List[System.IO.FileInfo]'
	$dirList = New-Object 'System.Collections.Generic.List[System.IO.DirectoryInfo]'
	# [System.IO.FileAttributes]::Directory -eq 16

	foreach ($item in $gotcha)
	{
		if (($item.Attributes -band 16) -eq 16)
			{ $dirList.Add($item) }
		else
			{ $fileList.Add($item) }
	}

	$ref = Get-Comparable $fileList

	$syncDir = [System.IO.Directory]::CreateDirectory($Synchronized)
	if ($syncDir -eq $null) { return $false }

	$syncFileList = Get-ChildItem -LiteralPath $syncDir.FullName -File
	$sync = Get-Comparable $syncFileList
	if ($sync -eq $null -or $ref -eq $null)
	{
		$copyList = $fileList
	}
	else
	{
		$compasion = Compare-Object -ReferenceObject $ref -DifferenceObject $sync
		foreach ($item in $compasion)
		{
			if ($item.SideIndicator -eq '<=')
			{
				$copyList.Add($item.InputObject.Info)
			}
		}
	}

	foreach ($item in $dirList)
	{
		Start-FolderSync -Reference $item.FullName -Synchronized $($syncDir.FullName + '\' + $item.Name)
	}
	
	foreach ($item in $copyList)
	{
		Copy-Item -Path $item.FullName -Destination $syncDir.FullName -Verbose
	}
}
