// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed 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.

goog.provide('goog.testing.fs.EntryTest');
goog.setTestOnly('goog.testing.fs.EntryTest');

goog.require('goog.fs.DirectoryEntry');
goog.require('goog.fs.Error');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.TestCase');
goog.require('goog.testing.fs.FileSystem');
goog.require('goog.testing.jsunit');

var fs, file, mockClock;

function setUpPage() {
  // This test has a tendency to timeout on external Travis testing
  // infrastructure. Up to 5s from 1s.
  goog.testing.TestCase.getActiveTestCase().promiseTimeout = 5000;
}

function setUp() {
  // Install the MockClock to create predictable timestamps for new files.
  mockClock = new goog.testing.MockClock(true);

  fs = new goog.testing.fs.FileSystem();
  file = fs.getRoot()
             .getDirectorySync('foo', goog.fs.DirectoryEntry.Behavior.CREATE)
             .getFileSync('bar', goog.fs.DirectoryEntry.Behavior.CREATE);

  // Uninstall the MockClock since it interferes with goog.Promise execution.
  // Tests that require specific timing may reinstall the MockClock and manually
  // advance promises using mockClock.tick().
  mockClock.uninstall();
}

function testGetName() {
  assertEquals('bar', file.getName());
}

function testGetFullPath() {
  assertEquals('/foo/bar', file.getFullPath());
  assertEquals('/', fs.getRoot().getFullPath());
}

function testGetFileSystem() {
  assertEquals(fs, file.getFileSystem());
}

function testMoveTo() {
  return file.moveTo(fs.getRoot()).then(function(newFile) {
    assertTrue(file.deleted);
    assertFalse(newFile.deleted);
    assertEquals('/bar', newFile.getFullPath());
    assertEquals(fs.getRoot(), newFile.parent);
    assertEquals(newFile, fs.getRoot().getFileSync('bar'));
    assertFalse(fs.getRoot().getDirectorySync('foo').hasChild('bar'));
  });
}

function testMoveToNewName() {
  // Advance the clock to an arbitrary, known time.
  mockClock.install();
  mockClock.tick(71);
  var promise = file.moveTo(fs.getRoot(), 'baz')
                    .then(function(newFile) {
                      mockClock.tick();
                      assertTrue(file.deleted);
                      assertFalse(newFile.deleted);
                      assertEquals('/baz', newFile.getFullPath());
                      assertEquals(fs.getRoot(), newFile.parent);
                      assertEquals(newFile, fs.getRoot().getFileSync('baz'));

                      var oldParentDir = fs.getRoot().getDirectorySync('foo');
                      assertFalse(oldParentDir.hasChild('bar'));
                      assertFalse(oldParentDir.hasChild('baz'));

                      return oldParentDir.getLastModified();
                    })
                    .then(function(lastModifiedDate) {
                      assertEquals(71, lastModifiedDate.getTime());
                      var oldParentDir = fs.getRoot().getDirectorySync('foo');
                      return oldParentDir.getMetadata();
                    })
                    .then(function(metadata) {
                      assertEquals(71, metadata.modificationTime.getTime());
                      return fs.getRoot().getLastModified();
                    })
                    .then(function(rootLastModifiedDate) {
                      assertEquals(71, rootLastModifiedDate.getTime());
                      return fs.getRoot().getMetadata();
                    })
                    .then(function(rootMetadata) {
                      assertEquals(71, rootMetadata.modificationTime.getTime());
                    })
                    .thenAlways(function() { mockClock.uninstall(); });
  mockClock.tick();
  return promise;
}

function testMoveDeletedFile() {
  return assertFailsWhenDeleted(function() {
    return file.moveTo(fs.getRoot());
  });
}

function testCopyTo() {
  mockClock.install();
  mockClock.tick(61);
  var promise = file.copyTo(fs.getRoot())
                    .then(function(newFile) {
                      assertFalse(file.deleted);
                      assertFalse(newFile.deleted);
                      assertEquals('/bar', newFile.getFullPath());
                      assertEquals(fs.getRoot(), newFile.parent);
                      assertEquals(newFile, fs.getRoot().getFileSync('bar'));

                      var oldParentDir = fs.getRoot().getDirectorySync('foo');
                      assertEquals(file, oldParentDir.getFileSync('bar'));
                      return oldParentDir.getLastModified();
                    })
                    .then(function(lastModifiedDate) {
                      assertEquals(
                          'The original parent directory was not modified.', 0,
                          lastModifiedDate.getTime());
                      var oldParentDir = fs.getRoot().getDirectorySync('foo');
                      return oldParentDir.getMetadata();
                    })
                    .then(function(metadata) {
                      assertEquals(
                          'The original parent directory was not modified.', 0,
                          metadata.modificationTime.getTime());
                      return fs.getRoot().getLastModified();
                    })
                    .then(function(rootLastModifiedDate) {
                      assertEquals(61, rootLastModifiedDate.getTime());
                      return fs.getRoot().getMetadata();
                    })
                    .then(function(rootMetadata) {
                      assertEquals(61, rootMetadata.modificationTime.getTime());
                    })
                    .thenAlways(function() { mockClock.uninstall(); });
  mockClock.tick();
  return promise;
}

function testCopyToNewName() {
  return file.copyTo(fs.getRoot(), 'baz').addCallback(function(newFile) {
    assertFalse(file.deleted);
    assertFalse(newFile.deleted);
    assertEquals('/baz', newFile.getFullPath());
    assertEquals(fs.getRoot(), newFile.parent);
    assertEquals(newFile, fs.getRoot().getFileSync('baz'));
    assertEquals(file, fs.getRoot().getDirectorySync('foo').getFileSync('bar'));
    assertFalse(fs.getRoot().getDirectorySync('foo').hasChild('baz'));
  });
}

function testCopyDeletedFile() {
  return assertFailsWhenDeleted(function() {
    return file.copyTo(fs.getRoot());
  });
}

function testRemove() {
  mockClock.install();
  mockClock.tick(57);
  var promise = file.remove()
                    .then(function() {
                      mockClock.tick();
                      var parentDir = fs.getRoot().getDirectorySync('foo');

                      assertTrue(file.deleted);
                      assertFalse(parentDir.hasChild('bar'));

                      return parentDir.getLastModified();
                    })
                    .then(function(date) {
                      assertEquals(57, date.getTime());
                      var parentDir = fs.getRoot().getDirectorySync('foo');
                      return parentDir.getMetadata();
                    })
                    .then(function(metadata) {
                      assertEquals(57, metadata.modificationTime.getTime());
                    })
                    .thenAlways(function() { mockClock.uninstall(); });
  mockClock.tick();
  return promise;
}

function testRemoveDeletedFile() {
  return assertFailsWhenDeleted(function() { return file.remove(); });
}

function testGetParent() {
  return file.getParent().then(function(p) {
    assertEquals(file.parent, p);
    assertEquals(fs.getRoot().getDirectorySync('foo'), p);
    assertEquals('/foo', p.getFullPath());
  });
}

function testGetDeletedFileParent() {
  return assertFailsWhenDeleted(function() { return file.getParent(); });
}

function assertFailsWhenDeleted(fn) {
  return file.remove().then(fn).then(
      function() { fail('Expected an error'); },
      function(err) {
        assertEquals(goog.fs.Error.ErrorCode.NOT_FOUND, err.code);
      });
}
