/**
 * 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;

import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.test.GenericTestUtils;

/**
 * Testing the correctness of FileSystem.getFileBlockLocations.
 */
public class TestGetFileBlockLocations {
  private static String TEST_ROOT_DIR = GenericTestUtils.getTempPath(
      "testGetFileBlockLocations");
  private static final int FileLength = 4 * 1024 * 1024; // 4MB
  private Configuration conf;
  private Path path;
  private FileSystem fs;
  private Random random;

  @BeforeEach
  public void setUp() throws IOException {
    conf = new Configuration();
    Path rootPath = new Path(TEST_ROOT_DIR);
    path = new Path(rootPath, "TestGetFileBlockLocations");
    fs = rootPath.getFileSystem(conf);
    FSDataOutputStream fsdos = fs.create(path, true);
    byte[] buffer = new byte[1024];
    while (fsdos.getPos() < FileLength) {
      fsdos.write(buffer);
    }
    fsdos.close();
    random = new Random(System.nanoTime());
  }

  private void oneTest(int offBegin, int offEnd, FileStatus status)
      throws IOException {
    if (offBegin > offEnd) {
      int tmp = offBegin;
      offBegin = offEnd;
      offEnd = tmp;
    }
    BlockLocation[] locations =
        fs.getFileBlockLocations(status, offBegin, offEnd - offBegin);
    if (offBegin < status.getLen()) {
      Arrays.sort(locations, new Comparator<BlockLocation>() {

        @Override
        public int compare(BlockLocation arg0, BlockLocation arg1) {
          long cmprv = arg0.getOffset() - arg1.getOffset();
          if (cmprv < 0) return -1;
          if (cmprv > 0) return 1;
          cmprv = arg0.getLength() - arg1.getLength();
          if (cmprv < 0) return -1;
          if (cmprv > 0) return 1;
          return 0;
        }

      });
      offBegin = (int) Math.min(offBegin, status.getLen() - 1);
      offEnd = (int) Math.min(offEnd, status.getLen());
      BlockLocation first = locations[0];
      BlockLocation last = locations[locations.length - 1];
      assertTrue(first.getOffset() <= offBegin);
      assertTrue(offEnd <= last.getOffset() + last.getLength());
    } else {
      assertTrue(locations.length == 0);
    }
  }

  @AfterEach
  public void tearDown() throws IOException {
    fs.delete(path, true);
    fs.close();
  }

  @Test
  public void testFailureNegativeParameters() throws IOException {
    FileStatus status = fs.getFileStatus(path);
    try {
      BlockLocation[] locations = fs.getFileBlockLocations(status, -1, 100);
      fail("Expecting exception being throw");
    } catch (IllegalArgumentException e) {

    }

    try {
      BlockLocation[] locations = fs.getFileBlockLocations(status, 100, -1);
      fail("Expecting exception being throw");
    } catch (IllegalArgumentException e) {

    }
  }

  @Test
  public void testGetFileBlockLocations1() throws IOException {
    FileStatus status = fs.getFileStatus(path);
    oneTest(0, (int) status.getLen(), status);
    oneTest(0, (int) status.getLen() * 2, status);
    oneTest((int) status.getLen() * 2, (int) status.getLen() * 4, status);
    oneTest((int) status.getLen() / 2, (int) status.getLen() * 3, status);
    oneTest((int) status.getLen(), (int) status.getLen() * 2, status);
    for (int i = 0; i < 10; ++i) {
      oneTest((int) status.getLen() * i / 10, (int) status.getLen() * (i + 1)
          / 10, status);
    }
  }

  @Test
  public void testGetFileBlockLocations2() throws IOException {
    FileStatus status = fs.getFileStatus(path);
    for (int i = 0; i < 1000; ++i) {
      int offBegin = random.nextInt((int) (2 * status.getLen()));
      int offEnd = random.nextInt((int) (2 * status.getLen()));
      oneTest(offBegin, offEnd, status);
    }
  }
}
