/*
 * Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.hadoop.fs.swift;

import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.swift.http.SwiftRestClient;
import org.apache.hadoop.fs.swift.util.SwiftObjectPath;
import org.apache.hadoop.fs.swift.util.SwiftTestUtils;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import static org.apache.hadoop.fs.swift.util.SwiftTestUtils.compareByteArrays;
import static org.apache.hadoop.fs.swift.util.SwiftTestUtils.dataset;
import static org.apache.hadoop.fs.swift.util.SwiftTestUtils.readBytesToString;
import static org.apache.hadoop.fs.swift.util.SwiftTestUtils.readDataset;
import static org.apache.hadoop.fs.swift.util.SwiftTestUtils.writeDataset;

public class TestSwiftFileSystemRename extends SwiftFileSystemBaseTest {

  /**
   * Rename a file into a directory
   *
   * @throws Exception
   */
  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameFileIntoExistingDirectory() throws Exception {
    assumeRenameSupported();

    Path src = path("/test/olddir/file");
    createFile(src);
    Path dst = path("/test/new/newdir");
    fs.mkdirs(dst);
    rename(src, dst, true, false, true);
    Path newFile = path("/test/new/newdir/file");
    if (!fs.exists(newFile)) {
      String ls = ls(dst);
      LOG.info(ls(path("/test/new")));
      LOG.info(ls(path("/test/hadoop")));
      fail("did not find " + newFile + " - directory: " + ls);
    }
    assertTrue("Destination changed",
            fs.exists(path("/test/new/newdir/file")));
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameFile() throws Exception {
    assumeRenameSupported();

    final Path old = new Path("/test/alice/file");
    final Path newPath = new Path("/test/bob/file");
    fs.mkdirs(newPath.getParent());
    final FSDataOutputStream fsDataOutputStream = fs.create(old);
    final byte[] message = "Some data".getBytes();
    fsDataOutputStream.write(message);
    fsDataOutputStream.close();

    assertTrue(fs.exists(old));
    rename(old, newPath, true, false, true);

    final FSDataInputStream bobStream = fs.open(newPath);
    final byte[] bytes = new byte[512];
    final int read = bobStream.read(bytes);
    bobStream.close();
    final byte[] buffer = new byte[read];
    System.arraycopy(bytes, 0, buffer, 0, read);
    assertEquals(new String(message), new String(buffer));
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameDirectory() throws Exception {
    assumeRenameSupported();

    final Path old = new Path("/test/data/logs");
    final Path newPath = new Path("/test/var/logs");
    fs.mkdirs(old);
    fs.mkdirs(newPath.getParent());
    assertTrue(fs.exists(old));
    rename(old, newPath, true, false, true);
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameTheSameDirectory() throws Exception {
    assumeRenameSupported();

    final Path old = new Path("/test/usr/data");
    fs.mkdirs(old);
    rename(old, old, false, true, true);
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameDirectoryIntoExistingDirectory() throws Exception {
    assumeRenameSupported();

    Path src = path("/test/olddir/dir");
    fs.mkdirs(src);
    createFile(path("/test/olddir/dir/file1"));
    createFile(path("/test/olddir/dir/subdir/file2"));

    Path dst = path("/test/new/newdir");
    fs.mkdirs(dst);
    //this renames into a child
    rename(src, dst, true, false, true);
    assertExists("new dir", path("/test/new/newdir/dir"));
    assertExists("Renamed nested file1", path("/test/new/newdir/dir/file1"));
    assertPathDoesNotExist("Nested file1 should have been deleted",
            path("/test/olddir/dir/file1"));
    assertExists("Renamed nested subdir",
            path("/test/new/newdir/dir/subdir/"));
    assertExists("file under subdir",
            path("/test/new/newdir/dir/subdir/file2"));

    assertPathDoesNotExist("Nested /test/hadoop/dir/subdir/file2 still exists",
            path("/test/olddir/dir/subdir/file2"));
  }

  /**
   * trying to rename a directory onto itself should fail,
   * preserving everything underneath.
   */
  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameDirToSelf() throws Throwable {
    assumeRenameSupported();
    Path parentdir = path("/test/parentdir");
    fs.mkdirs(parentdir);
    Path child = new Path(parentdir, "child");
    createFile(child);

    rename(parentdir, parentdir, false, true, true);
    //verify the child is still there
    assertIsFile(child);
  }

  /**
   * Assert that root directory renames are not allowed
   *
   * @throws Exception on failures
   */
  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameRootDirForbidden() throws Exception {
    assumeRenameSupported();
    rename(path("/"),
            path("/test/newRootDir"),
            false, true, false);
  }

  /**
   * Assert that renaming a parent directory to be a child
   * of itself is forbidden
   *
   * @throws Exception on failures
   */
  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameChildDirForbidden() throws Exception {
    assumeRenameSupported();

    Path parentdir = path("/test/parentdir");
    fs.mkdirs(parentdir);
    Path childFile = new Path(parentdir, "childfile");
    createFile(childFile);
    //verify one level down
    Path childdir = new Path(parentdir, "childdir");
    rename(parentdir, childdir, false, true, false);
    //now another level
    fs.mkdirs(childdir);
    Path childchilddir = new Path(childdir, "childdir");
    rename(parentdir, childchilddir, false, true, false);
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameFileAndVerifyContents() throws IOException {
    assumeRenameSupported();

    final Path filePath = new Path("/test/home/user/documents/file.txt");
    final Path newFilePath = new Path("/test/home/user/files/file.txt");
    mkdirs(newFilePath.getParent());
    int len = 1024;
    byte[] dataset = dataset(len, 'A', 26);
    writeDataset(fs, filePath, dataset, len, len, false);
    rename(filePath, newFilePath, true, false, true);
    byte[] dest = readDataset(fs, newFilePath, len);
    compareByteArrays(dataset, dest, len);
    String reread = readBytesToString(fs, newFilePath, 20);
  }


  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testMoveFileUnderParent() throws Throwable {
    if (!renameSupported()) return;
    Path filepath = path("test/file");
    createFile(filepath);
    //HDFS expects rename src, src -> true
    rename(filepath, filepath, true, true, true);
    //verify the file is still there
    assertIsFile(filepath);
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testMoveDirUnderParent() throws Throwable {
    if (!renameSupported()) {
      return;
    }
    Path testdir = path("test/dir");
    fs.mkdirs(testdir);
    Path parent = testdir.getParent();
    //the outcome here is ambiguous, so is not checked
    fs.rename(testdir, parent);
    assertExists("Source directory has been deleted ", testdir);
  }

  /**
   * trying to rename a file onto itself should succeed (it's a no-op)
   */
  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameFileToSelf() throws Throwable {
    if (!renameSupported()) return;
    Path filepath = path("test/file");
    createFile(filepath);
    //HDFS expects rename src, src -> true
    rename(filepath, filepath, true, true, true);
    //verify the file is still there
    assertIsFile(filepath);
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenamedConsistence() throws IOException {
    assumeRenameSupported();
    describe("verify that overwriting a file with new data doesn't impact" +
            " the existing content");

    final Path filePath = new Path("/test/home/user/documents/file.txt");
    final Path newFilePath = new Path("/test/home/user/files/file.txt");
    mkdirs(newFilePath.getParent());
    int len = 1024;
    byte[] dataset = dataset(len, 'A', 26);
    byte[] dataset2 = dataset(len, 'a', 26);
    writeDataset(fs, filePath, dataset, len, len, false);
    rename(filePath, newFilePath, true, false, true);
    SwiftTestUtils.writeAndRead(fs, filePath, dataset2, len, len, false, true);
    byte[] dest = readDataset(fs, newFilePath, len);
    compareByteArrays(dataset, dest, len);
    String reread = readBytesToString(fs, newFilePath, 20);
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenameMissingFile() throws Throwable {
    assumeRenameSupported();
    Path path = path("/test/RenameMissingFile");
    Path path2 = path("/test/RenameMissingFileDest");
    mkdirs(path("test"));
    rename(path, path2, false, false, false);
  }

  @Test(timeout = SWIFT_TEST_TIMEOUT)
  public void testRenamePseudoDir() throws Throwable {
    assumeRenameSupported();

    // create file directory (don't create directory file)
    SwiftRestClient client;
    client = SwiftRestClient.getInstance(fs.getUri(), fs.getConf());
    SwiftObjectPath path = SwiftObjectPath.fromPath(fs.getUri(), new Path("/test/olddir/file"));
    client.upload(path, new ByteArrayInputStream(new byte[0]), 0);

    rename(path("/test/olddir"), path("/test/newdir"), true, false, true);
    SwiftTestUtils.assertIsDirectory(fs, path("/test/newdir"));
    assertIsFile(path("/test/newdir/file"));
  }
}
