using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Security.Cryptography;

namespace Orciid.Media
{
	public class DiskMediaCache: MediaCache
	{
		private string cacheroot;

		public DiskMediaCache(string cacheroot)
		{
			this.cacheroot = cacheroot;
		}

		public override Stream WriteFile(string filename)
		{
			string f = GetCacheFilePath(filename);
			return File.Create(f);
		}

		public override Stream ReadFile(string filename, DateTime maxage)
		{
			string f = GetCacheFilePath(filename);
			if (!File.Exists(f) || File.GetCreationTime(f) < maxage)
				return null;
			return File.OpenRead(f);
		}

		private struct FileScore
		{
			public string File;
			public int Score;
			public long Size;
		}

		private class FileScoreComparer: IComparer
		{
			public int Compare(object x, object y)
			{
				FileScore a = (FileScore)x;
				FileScore b = (FileScore)y;
				return a.Score.CompareTo(b.Score);
			}
		}

		private long CollectFiles(string directory, CacheFileScorer scorer, ref ArrayList files)
		{
			long totalsize = 0;
			foreach (string dir in Directory.GetDirectories(directory))
				totalsize += CollectFiles(Path.Combine(directory, dir), scorer, ref files);
			foreach (FileInfo file in new DirectoryInfo(directory).GetFiles())
			{
				FileScore fs;
				fs.File = Path.Combine(directory, file.Name);
				fs.Size = file.Length;
				fs.Score = scorer(file.CreationTime, file.LastAccessTime, 0, file.Length);
				files.Add(fs);
				totalsize += file.Length;
			}
			return totalsize;
		}
			
		public override void CleanUp(CacheFileScorer scorer, long maxcachesize)
		{
			ArrayList files = new ArrayList();
			long totalsize = CollectFiles(cacheroot, scorer, ref files);	

			if (totalsize <= maxcachesize)
				return;
            
			files.Sort(new FileScoreComparer());
			int index = 0;
			while (index < files.Count && totalsize > maxcachesize)
			{
				try
				{
					FileScore s = (FileScore)files[index];
					File.Delete(s.File);
					files.RemoveAt(index);
					totalsize -= s.Size;
				}
				catch
				{
					index++;
				}
			}
		}

		private string GetCacheFilePath(string filename)
		{
			string hash = GetMD5(filename);
			string p1 = hash.Substring(0, 2);
			string p2 = hash.Substring(2, 2);
			string dir = Path.Combine(cacheroot, Path.Combine(p1, p2));
			Directory.CreateDirectory(dir);
			return Path.Combine(dir, filename);
		}

		private string GetMD5(string val)
		{
			byte[] b = new Byte[val.Length];
			for (int i = 0; i < val.Length; i++)
				b[i] = Convert.ToByte(val[i]);
			byte[] hash = new MD5CryptoServiceProvider().ComputeHash(b);
			StringBuilder result = new StringBuilder(hash.Length * 2);
			for (int i = 0; i < hash.Length; i++)
				result.Append(String.Format("{0,2:X}", hash[i]).Replace(" ", "0"));
			return result.ToString();
		}
	}
}
