/*
 * Subvolume.vala
 *
 * Copyright 2012-2018 Tony George <teejeetech@gmail.com>
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 *
 */

using TeeJee.Logging;
using TeeJee.FileSystem;
using TeeJee.JsonHelper;
using TeeJee.ProcessHelper;
using TeeJee.GtkHelper;
using TeeJee.System;
using TeeJee.Misc;

public class Subvolume : GLib.Object{

	public string device_uuid;
	public string name = "";
	public string path = "";
	public long id = -1;
	public int64 total_bytes = 0;
	public int64 unshared_bytes = 0;

	public string mount_path = "";

	//parent
	public SnapshotRepo? repo;
	
	public Subvolume(string name, string path, string parent_dev_uuid, SnapshotRepo? parent_repo){
		
		this.name = name;
		this.path = path;
		this.device_uuid = parent_dev_uuid;
		this.repo = parent_repo;

		if (repo != null){
			this.mount_path = repo.mount_paths[name];
		}
	}

	public string total_formatted{
		owned get{
			return format_file_size(total_bytes);
		}
	}

	public string unshared_formatted{
		owned get{
			return format_file_size(unshared_bytes);
		}
	}

	public Device? get_device(){
		
		return Device.get_device_by_uuid(device_uuid);
	}
	
	public bool exists_on_disk{
		get {
			return dir_exists(path);
		}
	}

	public bool is_system_subvolume{
		get {
			return (repo == null);
		}
	}
	
	public static Gee.HashMap<string, Subvolume> detect_subvolumes_for_system_by_path(
		string system_path, SnapshotRepo? repo, Gtk.Window? parent_window){

		var map = new Gee.HashMap<string, Subvolume>();
		
		log_debug("Searching subvolume for system at path: %s".printf(system_path));
		
		var fstab = FsTabEntry.read_file(path_combine(system_path, "/etc/fstab"));
		var crypttab = CryptTabEntry.read_file(path_combine(system_path, "/etc/crypttab"));
		
		foreach(var item in fstab){
			
			if (!item.is_for_system_directory()){ continue; }
			
			if (item.subvolume_name().length > 0){
				
				var dev = item.resolve_device(crypttab, parent_window);
				var dev_name = (dev == null) ? "" : dev.device;
				var dev_uuid = (dev == null) ? "" : dev.uuid;
				
				log_debug("Found subvolume: %s, on device: %s".printf(item.subvolume_name(), dev_name));
				
				var subvol = new Subvolume(item.subvolume_name(), item.mount_point, dev_uuid, repo);
				map.set(subvol.name, subvol);
			}
		}

		return map;
	}

	public void print_info(){
		
		log_debug("name=%s, uuid=%s, id=%ld, path=%s".printf(name, device_uuid, id, path));
	}

	// actions ----------------------------------
	
	public bool remove(){

		if (is_system_subvolume){
			if (name == "@"){
				path = path_combine(App.mount_point_app + "/backup", "@");
			}
			else if (name == "@home"){
				path = path_combine(App.mount_point_app + "/backup-home", "@home");
			}
		}
		
		string cmd = "";
		string std_out, std_err, subpath;
		int ret_val;

		if (!dir_exists(path)){ return true; } // ok, item does not exist

		log_msg("%s: %s (Id:%ld)".printf(_("Deleting subvolume"), name, id));

		string options = App.use_option_raw ? "--commit-after" : "";
		
		subpath = path_combine(path, name);
		if (dir_exists(subpath)) { // there is a nested subvol to remove first
			cmd = "btrfs subvolume delete %s '%s'".printf(options, subpath);
			log_debug("Deleting nested subvolume in snapshot");
			log_debug(cmd);
			ret_val = exec_sync(cmd, out std_out, out std_err);
			if (ret_val != 0){
				log_error(std_err);
				log_error(_("Failed to delete snapshot nested subvolume") + ": '%s'".printf(path));
				return false;
			}
		}
		
		cmd = "btrfs subvolume delete %s '%s'".printf(options, path);
		log_debug(cmd);
		ret_val = exec_sync(cmd, out std_out, out std_err);
		if (ret_val != 0){
			log_error(std_err);
			log_error(_("Failed to delete snapshot subvolume") + ": '%s'".printf(path));
			return false;
		}

		log_msg("%s: %s (Id:%ld)\n".printf(_("Deleted subvolume"), name, id));

		if (App.btrfs_qgroups_enabled) {
			if ((id > 0) && (repo != null)){
				log_msg("%s: 0/%ld".printf(_("Destroying qgroup"), id));

				cmd = "btrfs qgroup destroy 0/%ld '%s'".printf(id, repo.mount_paths[name]);
				log_debug(cmd);
				ret_val = exec_sync(cmd, out std_out, out std_err);
				if (ret_val != 0){
					log_error(_("Failed to destroy qgroup") + ": '0/%ld'".printf(id));
					return false;
				}

				log_msg("%s: 0/%ld\n".printf(_("Destroyed qgroup"), id));
			}
		}

		return true;
	}

	public bool restore(){

		if (is_system_subvolume) { return false; }

		// restore snapshot subvolume by creating new subvolume snapshots ----------------------
		
		string src_path = path;
		string dst_path = path_combine(mount_path, name);

		if (!dir_exists(src_path)){
			log_error("%s: %s".printf(_("Not Found"), src_path));
			return false;
		}

		if (dir_exists(dst_path)){
			log_error("%s: %s".printf(_("Subvolume exists at destination"), dst_path));
			return false;
		}
		
		string cmd = "btrfs subvolume snapshot '%s' '%s'".printf(src_path, dst_path);
		log_debug(cmd);

		string std_out, std_err;
		int status = exec_sync(cmd, out std_out, out std_err);
		
		if (status != 0){
			log_error(std_err);
			log_error(_("btrfs returned an error") + ": %d".printf(status));
			log_error(_("Failed to restore system subvolume") + ": %s".printf(name));
			return false;
		}

		log_msg(_("Restored system subvolume") + ": %s".printf(name));
		
		return true;
	}
}
