// Created with MonoDevelop
//
//    Copyright (C) 2017 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.



using System;
using System.IO;
using System.Text;

using Hiperscan.FileIO;
using Hiperscan.Unix;

using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using System.Collections.Generic;


namespace Hiperscan.FileIO
{

	public delegate bool ZipCondition(string path);	
	public delegate void ParentDirectoryChangedHandler(string directory);
	
	
	public class Zip : System.IDisposable
	{

		private ZipOutputStream zip_output_stream;
		private string zip_filename;
		private FileStream zip_fstream;

		public ZipCondition AddToArchiveCondition { get; set; }


		public Zip(string zip_fname, bool overwrite)
		{
			if (zip_fname.ToLower().EndsWith(".zip") == false)
				zip_fname += ".zip";

			this.zip_filename = zip_fname;

			if (File.Exists(this.zip_filename))
			{
				if (overwrite == false)
					throw new Exception(Catalog.GetString("Archive file already exists."));

				File.Delete(this.zip_filename);
			}

			this.zip_fstream = File.Create(zip_fname);
			this.zip_output_stream = new ZipOutputStream(this.zip_fstream);
			this.zip_output_stream.IsStreamOwner = true;

			this.CompressionLevel = 3;
		}

		public Zip(string zip_fname) : this(zip_fname, false)
		{
		}

		~Zip()
		{
			this.Finish();
		}

		public void Dispose()
		{
			this.Finish();
		}

		public void Add(string path)
		{
			this.Add(path, Path.GetFileName(path));
		}

		public void Add(List<string> paths)
		{
			foreach (string path in paths)
			{
				this.Add(path);
			}
		}

		public void Add(FileInfo file)
		{
			this.Add(file.FullName);
		}

		public void Add(List<FileInfo> files)
		{
			foreach (FileInfo file in files)
			{
				this.Add(file);
			}
		}

		public void Add(string path, string target)
		{
			if (Directory.Exists(path))
				this.AddDirectoryContent(path, target);
			else if (File.Exists(path))
				this.AddFile(path, target);
			else
				throw new FileNotFoundException(Catalog.GetString("Cannot find file or directory:") + " " + path);
		}

		public void AddDirectoryContent(string dir_path)
		{
			this.AddDirectoryContent(dir_path, string.Empty);
		}

		public void AddDirectoryContent(string dir_path, string target)
		{
			if (Directory.Exists(dir_path) == false)
				throw new FileNotFoundException(Catalog.GetString("Cannot find directory:") + " " + dir_path);

			foreach (string path in Directory.GetDirectories(dir_path, "*", SearchOption.TopDirectoryOnly))
			{
				if (string.IsNullOrEmpty(target))
					this.Add(path, Path.GetFileName(path));
				else
					this.Add(path, Path.Combine(target, Path.GetFileName(path)));
			}

			foreach (string path in Directory.GetFiles(dir_path, "*", SearchOption.TopDirectoryOnly))
			{
				if (string.IsNullOrEmpty(target))
					this.Add(path, Path.GetFileName(path));
				else
					this.Add(path, Path.Combine(target, Path.GetFileName(path)));
			}
		}

		private void AddFile(string path, string target)
		{
			if (this.zip_fstream == null)
				throw new Exception(Catalog.GetString("Cannot add file to finished archive file."));

			if (this.AddToArchiveCondition != null)
			{
				if (this.AddToArchiveCondition(path) == false)
					return;
			}
			path = path.Replace("\\", "/");
			target = target.Replace("\\", "/");

			ZipEntry zip_entry = new ZipEntry(FileIOHelper.ConvertToValidPathName(target));
			FileInfo file = new FileInfo(path);
			zip_entry.DateTime = file.LastWriteTime;
			zip_entry.Size = file.Length;

			this.zip_output_stream.PutNextEntry(zip_entry);
		  	using (FileStream stream = File.OpenRead(path)) 
			{
                stream.CopyTo(this.zip_output_stream);
        	}

        	this.zip_output_stream.CloseEntry();
		}

		public void Finish()
		{
			lock (this)
			{
				if (this.zip_output_stream == null)
					return;

				this.zip_output_stream.Finish();
				this.zip_output_stream.Flush();
				this.zip_output_stream = null;

				this.zip_fstream.Close();
				this.zip_fstream = null;
			}
		}

		public static void Decompress(string zip_fname, string out_path, string dirname)
		{
			string path = Path.Combine(out_path, dirname);
			if (Directory.Exists(path))
				Directory.Delete(path, true);

			Directory.CreateDirectory(path);

			Zip.Decompress(zip_fname, path);
		}

		public static void Decompress(string zip_fname, string out_path)
		{
			FileStream stream = File.OpenRead(zip_fname);
        	
			ZipFile zip_file = new ZipFile(stream);

			foreach (ZipEntry zip_entry in zip_file) 
			{
				string entry_name = zip_entry.Name;
				if (zip_entry.IsFile == true)
				{
					string fname = Path.Combine(out_path, entry_name);
					FileInfo file = new FileInfo(fname);

					if (file.Directory.Exists == false)
						file.Directory.Create();

					Stream zip_stream = zip_file.GetInputStream(zip_entry);

					using (FileStream file_stream = File.Create(fname)) 
					{
                        zip_stream.CopyTo(file_stream);
					}
				}
			}

			zip_file.Close();
		}

		private static string ConvertZipEntryEncoding(string zip_entry_name)
		{
			byte[] utf8_encoding_bytes = Encoding.UTF8.GetBytes(zip_entry_name);
					
      		Encoding default_encoding = Encoding.Default;
 			byte[] default_bytes = Encoding.Convert(Encoding.UTF8, default_encoding, utf8_encoding_bytes);    				
      		char[] default_chars = new char[default_encoding.GetCharCount(default_bytes, 0, default_bytes.Length)];
					
      		default_encoding.GetChars(default_bytes, 0, default_bytes.Length, default_chars, 0);

     		return new string(default_chars);
		}

		public int CompressionLevel
		{
			get
			{
				return this.zip_output_stream.GetLevel();
			}
			set
			{
				if (this.zip_output_stream != null)
					this.zip_output_stream.SetLevel(value);
			}
		}
	}
}

