/*******************************************************************************
 * Copyright (c) 2009, 2017 Cloudsmith Inc. and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Cloudsmith Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.p2.tests.core;

import java.io.*;
import org.eclipse.equinox.internal.p2.touchpoint.natives.BackupStore;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;

public class BackupTest extends AbstractProvisioningTest {
	private static final String BUPREFIX = "BackupTest";
	private File sourceDir;
	private File aDir;
	private File aaDir;
	private File bDir;
	private File aTxt;
	private File bTxt;
	private File abDir;
	private File cTxt;
	private File cTxtRelative;

	/**
	 * Sets up directories and files under user.home
	 * <ul><li>P2BUTEST/</li>
	 *     <ul><li>A/</li>
	 *         <ul><li>AA/</li>
	 *             <ul><li>a.txt</li>
	 *                 <li>b.txt</li>
	 *             </ul>
	 *         </ul>
	 *         <li>B/</li>
	 *     </ul>
	 * </ul>
	 */
	@Override
	public void setUp() {
		// create some test files under user.home
		// do not want them under /tmp as it may be on its own file system (and even
		// be an in-memory file system).
		//
		String userHome = System.getProperty("user.home");
		sourceDir = new File(new File(userHome), "P2BUTEST");
		aDir = new File(sourceDir, "A");
		aDir.mkdirs();
		aaDir = new File(aDir, "AA");
		aaDir.mkdir();
		abDir = new File(aDir, "AB");
		abDir.mkdir();

		bDir = new File(sourceDir, "B");
		bDir.mkdirs();
		aTxt = new File(aaDir, "a.txt");
		bTxt = new File(aaDir, "b.txt");
		cTxt = new File(abDir, "c.txt");
		cTxtRelative = new File(aaDir, "../AB/c.txt");
		try {
			writeToFile(aTxt, "A\nA file with an A");
			writeToFile(bTxt, "B\nA file with a B");
			writeToFile(cTxt, "C\nA file with a C");
		} catch (IOException e) {
			fail();
		}

	}

	private void writeToFile(File file, String content) throws IOException {
		file.getParentFile().mkdirs();
		file.createNewFile();

		try (Writer writer = new BufferedWriter(new FileWriter(file))) {
			writer.write(content);
		}
	}

	@Override
	public void tearDown() {
		fullyDelete(sourceDir);
	}

	/**
	 * Deletes a file, or a directory with all of it's children.
	 * @param file the file or directory to fully delete
	 * @return true if, and only if the file is deleted
	 */
	private boolean fullyDelete(File file) {
		if (!file.exists())
			return true;
		if (file.isDirectory()) {
			File[] children = file.listFiles();
			for (File child : children) {
				if (!fullyDelete(new File(file, child.getName()))) {
					return false;
				}
			}
		}
		return file.delete();
	}

	/**
	 * Test that a path containing ".." can be backed up and restored.
	 */
	public void testBackupRelative() {
		BackupStore store = new BackupStore(null, BUPREFIX);
		// backup and overwrite a.txt
		try {
			store.backup(cTxtRelative);
		} catch (IOException e) {
			e.printStackTrace();
			fail("IO Exception when backing up cTxtRelative");
		}
		if (cTxt.exists())
			fail("File not moved to backup - still exists");
		try {
			writeToFile(cTxt, "XXXX\n- This file should be restored with C");
		} catch (IOException e) {
			e.printStackTrace();
			fail("Could not write a file for testing purposes.");
		}

		// restore
		try {
			store.restore();
		} catch (IOException e) {
			e.printStackTrace();
			fail("Restore operation failed with IOException");
		}
		// assert restore
		assertFileContent("Restore of C failed - not original content", cTxt, "C");
		assertNoGarbage(store);
	}

	public void testBackupRestore() {
		BackupStore store = new BackupStore(null, BUPREFIX);
		// backup and overwrite a.txt
		try {
			store.backup(aTxt);
		} catch (IOException e) {
			e.printStackTrace();
			fail("IO Exception when backing up aTxt");
		}
		if (aTxt.exists())
			fail("File not moved to backup - still exists");
		try {
			writeToFile(aTxt, "XXXX\n- This file should be restored with A");
		} catch (IOException e) {
			e.printStackTrace();
			fail("Could not write a file for testing purposes.");
		}

		// backup the empty B directory
		try {
			store.backup(bDir);
		} catch (IOException e) {
			e.printStackTrace();
			fail("IO Exception when backing up bDir");
		}
		if (bDir.exists())
			fail("Backed up directory was not moved");

		// backup b as a copy
		try {
			store.backupCopy(bTxt);
			assertFileContent("File should have been copied", bTxt, "B");
		} catch (IOException e) {
			fail("Could not backupCopy bTxt");
		}

		// restore
		try {
			store.restore();
		} catch (IOException e) {
			e.printStackTrace();
			fail("Restore operation failed with IOException");
		}

		// assert restore
		assertFileContent("Restore of A failed - not original content", aTxt, "A");
		if (!bDir.isDirectory() && bDir.listFiles().length != 0)
			fail("Empty directory not restored ok");

		assertNoGarbage(store);
	}

	public void testBackupDiscard() {
		BackupStore store = new BackupStore(null, BUPREFIX);
		// backup and overwrite a.txt
		try {
			store.backup(aTxt);
		} catch (IOException e) {
			e.printStackTrace();
			fail("IO Exception when backing up aTxt");
		}
		if (aTxt.exists())
			fail("File not moved to backup - still exists");
		try {
			writeToFile(aTxt, "XXXX\n- This file should be restored with A");
		} catch (IOException e) {
			e.printStackTrace();
			fail("Could not write a file for testing purposes.");
		}

		// backup the empty B directory
		try {
			store.backup(bDir);
		} catch (IOException e) {
			e.printStackTrace();
			fail("IO Exception when backing up bDir");
		}
		if (bDir.exists())
			fail("Backed up directory was not moved");

		// restore
		store.discard();

		// assert discard
		assertFileContent("Discard of A failed - not new content", aTxt, "XXXX");
		if (bDir.isDirectory())
			fail("Remove of empty directory not discarded ok");

		assertNoGarbage(store);
	}

	public void testBackupAll() {
		BackupStore store = new BackupStore(null, BUPREFIX);
		// backup and overwrite a.txt
		try {
			store.backupAll(aDir);
		} catch (IOException e) {
			e.printStackTrace();
			fail("IO Exception when backing up aDir");
		}
		if (aTxt.exists())
			fail("File not moved to backup - still exists");
		if (bTxt.exists())
			fail("File bTxt not moved to backup - still exists");

		try {
			writeToFile(aTxt, "XXXX\n- This file should be restored with A");
		} catch (IOException e) {
			e.printStackTrace();
			fail("Could not write a file for testing purposes.");
		}
		try {
			store.restore();
		} catch (IOException e) {
			fail("Restore failed");
		}
		assertFileContent("A not restored", aTxt, "A");
		assertFileContent("B not restored", bTxt, "B");
		assertNoGarbage(store);
	}

	public void testBackupCopyAll() {
		BackupStore store = new BackupStore(null, BUPREFIX);
		// backup and overwrite a.txt
		try {
			store.backupCopyAll(aDir);
		} catch (IOException e) {
			e.printStackTrace();
			fail("IO Exception when backing up aDir");
		}
		if (!aTxt.exists())
			fail("File not copied to backup - does not exist");
		if (!bTxt.exists())
			fail("File bTxt not copied to backup - does not exists");

		try {
			writeToFile(aTxt, "XXXX\n- This file should be restored with A");
			writeToFile(bTxt, "XXXX\n- This file should be restored with B");
		} catch (IOException e) {
			e.printStackTrace();
			fail("Could not write a file for testing purposes.");
		}
		try {
			store.restore();
		} catch (IOException e) {
			fail("Restore failed");
		}
		assertFileContent("A not restored", aTxt, "A");
		assertFileContent("B not restored", bTxt, "B");
		assertNoGarbage(store);
	}

	private void assertNoGarbage(BackupStore store) {
		File buDir = new File(store.getBackupRoot(), BUPREFIX);
		if (buDir.exists())
			fail("Backup directory not cleaned up");

		//		Set roots = store.getBackupRoots();
		//		if (roots.size() == 0)
		//			assertTrue("Root set is empty", true);
		//		for (Iterator itor = roots.iterator(); itor.hasNext();) {
		//			File root = (File) itor.next();
		//			File buDir = new File(root, BUPREFIX);
		//			if (buDir.exists())
		//				fail("Backup directory not cleaned up");
		//		}
	}
}
