/*
 * Syncany, www.syncany.org
 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@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 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/>.
 */
package org.syncany.config;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.syncany.config.to.DaemonConfigTO;
import org.syncany.config.to.FolderTO;
import org.syncany.config.to.UserTO;
import org.syncany.crypto.CipherUtil;
import org.syncany.operations.watch.WatchOperationOptions;
import org.syncany.util.FileUtil;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;

/**
 * The daemon helper provides helper functions to read and/or write the
 * daemon configuration file as defined by {@link DaemonConfigTO}.
 *
 * @author Vincent Wiencek <vwiencek@gmail.com>
 * @author Philipp C. Heckel <philipp.heckel@gmail.com>
 */
public class DaemonConfigHelper {
	private static final Logger logger = Logger.getLogger(DaemonConfigHelper.class.getSimpleName());

	public static UserTO getFirstDaemonUser(DaemonConfigTO daemonConfig) {
		List<UserTO> users = readWebSocketServerUsers(daemonConfig);

		if (users.size() > 0) {
			return users.get(0);
		}
		else {
			return null;
		}
	}

	/**
	 * Adds the given folder to the user-specific daemon configuration (<tt>daemon.xml</tt>).
	 *
	 * <p>The method first reads the daemon configuration, checks if the folder is already present
	 * and adds it if it is not. If no daemon config file exists, a new default config file is created
	 * via {@link #createAndWriteDefaultDaemonConfig(File)}. If the folder is already present in
	 * the current daemon config, <tt>false</tt> is returned. If an error occurs (e.g. an I/O error
	 * or an invalid XML file), a {@link ConfigException} is thrown. If the folder was successfully added,
	 * <tt>true</tt> is returned.
	 *
	 * @param localDir Absolute path of the local folder to add to the daemon config
	 * @return Returns <tt>true</tt> if the folder was successfully added to the daemon config,
	 *         <tt>false</tt> otherwise
	 * @throws ConfigException If an error occurs, e.g. an I/O error or an invalid XML file
	 */
	public static boolean addFolder(File localDir) throws ConfigException {
		File daemonConfigFile = new File(UserConfig.getUserConfigDir(), UserConfig.DAEMON_FILE);

		if (daemonConfigFile.exists()) {
			DaemonConfigTO daemonConfigTO = DaemonConfigTO.load(daemonConfigFile);
			String localDirPath = FileUtil.getCanonicalFile(localDir).getAbsolutePath();

			// Check if folder already exists
			boolean folderExists = false;

			for (FolderTO folderTO : daemonConfigTO.getFolders()) {
				if (localDirPath.equals(folderTO.getPath())) {
					folderExists = true;
					break;
				}
			}

			// Add to config if it's not already in there
			if (!folderExists) {
				logger.log(Level.INFO, "Adding folder to daemon config: " + localDirPath + ", and saving config at " + daemonConfigFile);

				daemonConfigTO.getFolders().add(new FolderTO(localDirPath));
				daemonConfigTO.save(daemonConfigFile);

				return true;
			}
			else {
				return false;
			}
		}
		else {
			FolderTO localDirFolderTO = new FolderTO(localDir.getAbsolutePath());
			createAndWriteDaemonConfig(daemonConfigFile, Arrays.asList(new FolderTO[] { localDirFolderTO }));

			return true;
		}
	}
	
	public static boolean removeFolder(File localDir) throws ConfigException {
		return removeFolder(localDir.getAbsolutePath());
	}
	
	public static boolean removeFolder(String localDirIdentifier) throws ConfigException {
		File daemonConfigFile = new File(UserConfig.getUserConfigDir(), UserConfig.DAEMON_FILE);

		if (daemonConfigFile.exists()) {
			DaemonConfigTO daemonConfigTO = DaemonConfigTO.load(daemonConfigFile);
			
			// Is index?
			Integer localDirIndex = Ints.tryParse(localDirIdentifier);
			boolean isLocalDirIndex = localDirIndex != null;
			boolean folderRemoved = false;
			
			// Remove by index
			if (isLocalDirIndex) {
				localDirIndex--;
				
				if (localDirIndex >= 0 && localDirIndex < daemonConfigTO.getFolders().size()) {
					logger.log(Level.INFO, "Given identifier (" + localDirIndex + ") is a valid index for " + daemonConfigTO.getFolders().get(localDirIndex).getPath() + ". REMOVING.");
					folderRemoved = null != daemonConfigTO.getFolders().remove((int) localDirIndex);
				}
				else {
					logger.log(Level.INFO, "Given identifier (" + localDirIndex + ") is a INVALID index. NOT REMOVING.");
				}
			}
			
			// Remove by name/path
			else {
				final String localDirPath = FileUtil.getCanonicalFile(new File(localDirIdentifier)).getAbsolutePath();
				
				folderRemoved = Iterables.removeIf(daemonConfigTO.getFolders(), new Predicate<FolderTO>() {
					@Override
					public boolean apply(FolderTO folder) {
						return folder.getPath().equals(localDirPath);
					}
				});	
			}			
						
			// Save (if removed)
			if (folderRemoved) {
				logger.log(Level.INFO, "Folder was removed. Saving daemon.xml ...");

				daemonConfigTO.save(daemonConfigFile);
				return true;
			}
			else {
				return false;
			}
		}
		else {
			createAndWriteDaemonConfig(daemonConfigFile, Arrays.asList(new FolderTO[] { }));
			return true;
		}
	}

    public static DaemonConfigTO createAndWriteDefaultDaemonConfig(File daemonConfigFile) throws ConfigException {
		return createAndWriteDaemonConfig(daemonConfigFile, new ArrayList<FolderTO>());
	}

    public static DaemonConfigTO createAndWriteExampleDaemonConfig(File daemonConfigFile) throws ConfigException {
    	File defaultFolder = new File(System.getProperty("user.home"), UserConfig.DEFAULT_FOLDER);

    	FolderTO defaultFolderTO = new FolderTO();
    	defaultFolderTO.setPath(defaultFolder.getAbsolutePath());
    	defaultFolderTO.setWatchOptions(new WatchOperationOptions());

    	return createAndWriteDaemonConfig(daemonConfigFile, Arrays.asList(new FolderTO[] { defaultFolderTO }));
	}

    public static DaemonConfigTO createAndWriteDaemonConfig(File configFile, List<FolderTO> folders) throws ConfigException {
    	UserTO defaultUserTO = new UserTO();
		defaultUserTO.setUsername(UserConfig.USER_ADMIN);
		defaultUserTO.setPassword(CipherUtil.createRandomAlphabeticString(12));

		ArrayList<UserTO> users = new ArrayList<>();
		users.add(defaultUserTO);

		DaemonConfigTO defaultDaemonConfigTO = new DaemonConfigTO();
		defaultDaemonConfigTO.setFolders(new ArrayList<>(folders));
		defaultDaemonConfigTO.setUsers(users);

		defaultDaemonConfigTO.save(configFile);

		return defaultDaemonConfigTO;
	}

	private static List<UserTO> readWebSocketServerUsers(DaemonConfigTO daemonConfigTO) {
		List<UserTO> users = daemonConfigTO.getUsers();

		if (users == null) {
			users = new ArrayList<UserTO>();
		}

		// Add CLI credentials
		if (daemonConfigTO.getPortTO() != null) {
			users.add(daemonConfigTO.getPortTO().getUser());
		}

		return users;
	}
}
