/*
 * 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.iotdb.db.storageengine.dataregion.compaction;

import org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.exception.MetadataException;
import org.apache.iotdb.commons.path.MeasurementPath;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.exception.StorageEngineException;
import org.apache.iotdb.db.storageengine.dataregion.compaction.schedule.CompactionScheduleContext;
import org.apache.iotdb.db.storageengine.dataregion.compaction.selector.impl.RewriteCrossSpaceCompactionSelector;
import org.apache.iotdb.db.storageengine.dataregion.compaction.selector.utils.CrossCompactionTaskResource;
import org.apache.iotdb.db.storageengine.dataregion.compaction.utils.TsFileGeneratorUtils;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResourceStatus;

import org.apache.tsfile.common.conf.TSFileDescriptor;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.exception.write.WriteProcessException;
import org.apache.tsfile.file.metadata.IDeviceID;
import org.apache.tsfile.file.metadata.PlainDeviceID;
import org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
import org.apache.tsfile.read.common.TimeRange;
import org.apache.tsfile.write.chunk.ChunkWriterImpl;
import org.apache.tsfile.write.chunk.IChunkWriter;
import org.apache.tsfile.write.writer.TsFileIOWriter;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * If an unsequence file is generated by a load operation or some files in sequence space is
 * deleted, some devices in it may not overlap with any files in sequence space, these devices'
 * startTime may larger than the latest endTime of it in sequence space. When performing a
 * cross-space compaction, the selected sequence file may not contain these devices, causing these
 * devices to be allocated to the first target file when performing the compaction.
 */
public class CrossSpaceCompactionWithUnusualCasesTest extends AbstractCompactionTest {

  @Before
  public void setUp()
      throws IOException, WriteProcessException, MetadataException, InterruptedException {
    super.setUp();
    IoTDBDescriptor.getInstance().getConfig().setEnableTsFileValidation(true);
    IoTDBDescriptor.getInstance().getConfig().setMinCrossCompactionUnseqFileLevel(0);
    TSFileDescriptor.getInstance().getConfig().setMaxNumberOfPointsInPage(10);
    TSFileDescriptor.getInstance().getConfig().setMaxDegreeOfIndexNode(3);
  }

  @After
  public void tearDown() throws IOException, StorageEngineException {
    super.tearDown();
  }

  @Test
  public void testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFiles1()
      throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    // selection
    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(3, result.get(0).getTotalFileNums());
  }

  @Test
  public void testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFiles2()
      throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3
    // device: d2, time: [210, 270]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource3.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource3, "d2", true, 210, 270);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource3.serialize();
    seqResources.add(seqTsFileResource3);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(3, result.get(0).getTotalFileNums());
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource2));
    Assert.assertTrue(result.get(0).getSeqFiles().contains(seqTsFileResource3));
  }

  @Test
  public void testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFiles3()
      throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3
    // device: d2, time: [210, 270]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource3.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource3, "d2", true, 210, 270);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource3.serialize();
    seqResources.add(seqTsFileResource3);

    // seq file 4
    // device: d3, time: [280, 290]
    TsFileResource seqTsFileResource4 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource4.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource4, "d3", true, 280, 290);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource4.serialize();
    seqResources.add(seqTsFileResource4);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(3, result.get(0).getTotalFileNums());
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource2));
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource4));
    Assert.assertTrue(result.get(0).getSeqFiles().contains(seqTsFileResource3));
  }

  @Test
  public void testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFiles4()
      throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3
    // device: d2, time: [210, 270]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource3.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource3, "d2", true, 210, 270);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource3.serialize();
    seqResources.add(seqTsFileResource3);

    // seq file 4
    // device: d3, time: [280, 290]
    TsFileResource seqTsFileResource4 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource4.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource4, "d3", true, 280, 290);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource4.serialize();
    seqResources.add(seqTsFileResource4);

    // seq file 5
    // device: d2, time: [800, 900]
    TsFileResource seqTsFileResource5 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource5.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource5, "d2", true, 800, 900);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource5.serialize();
    seqResources.add(seqTsFileResource5);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(3, result.get(0).getTotalFileNums());
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource2));
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource4));
    Assert.assertTrue(result.get(0).getSeqFiles().contains(seqTsFileResource5));
  }

  @Test
  public void testMultiUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFiles()
      throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3
    // device: d2, time: [210, 270]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource3.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource3, "d2", true, 210, 270);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource3.serialize();
    seqResources.add(seqTsFileResource3);

    // seq file 4
    // device: d3, time: [280, 290]
    TsFileResource seqTsFileResource4 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource4.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource4, "d3", true, 280, 290);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource4.serialize();
    seqResources.add(seqTsFileResource4);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    // unSeq file 2
    // device: d1, time: [400, 500]
    // device: d2, time: [500, 600]
    // device: d3, time: [100, 200]
    TsFileResource unSeqTsFileResource2 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource2, "d1", false, 400, 500);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource2, "d2", false, 500, 600);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource2, "d3", false, 100, 200);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource2.serialize();
    unseqResources.add(unSeqTsFileResource2);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(5, result.get(0).getTotalFileNums());
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource2));
  }

  @Test
  public void testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFiles5()
      throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d3, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d3", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    // selection
    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(2, result.get(0).getTotalFileNums());
  }

  @Test
  public void testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFiles6()
      throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3
    // device: d2, time: [210, 270]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource3.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource3, "d2", true, 210, 270);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource3.serialize();
    seqResources.add(seqTsFileResource3);

    // seq file 4
    // device: d3, time: [280, 290]
    TsFileResource seqTsFileResource4 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource4.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource4, "d3", true, 280, 290);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource4.serialize();
    seqResources.add(seqTsFileResource4);

    // seq file 5
    // device: d2, time: [500, 600]
    TsFileResource seqTsFileResource5 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource5.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource5, "d2", true, 500, 600);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource5.serialize();
    seqResources.add(seqTsFileResource5);

    // seq file 6
    // device: d2, time: [700, 800]
    TsFileResource seqTsFileResource6 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource6.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource6, "d2", true, 700, 800);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource6.serialize();
    seqResources.add(seqTsFileResource6);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(3, result.get(0).getTotalFileNums());
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource2));
    Assert.assertFalse(result.get(0).getSeqFiles().contains(seqTsFileResource4));
    Assert.assertTrue(result.get(0).getSeqFiles().contains(seqTsFileResource5));
  }

  @Test
  public void
      testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFilesWithUnclosedSeqFile1()
          throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2 (unclosed)
    // device: d2, time: [100, ]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    seqTsFileResource2.updateStartTime(new PlainDeviceID(COMPACTION_TEST_SG + ".d2"), 100);
    seqTsFileResource2.setStatusForTest(TsFileResourceStatus.UNCLOSED);
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    // selection
    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(0, result.size());
  }

  @Test
  public void
      testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFilesWithUnclosedSeqFile2()
          throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3 (unclosed)
    // device: d2, time: [610, 670]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    seqTsFileResource3.updateStartTime(new PlainDeviceID(COMPACTION_TEST_SG + ".d2"), 610);
    seqTsFileResource3.setStatusForTest(TsFileResourceStatus.UNCLOSED);

    seqTsFileResource3.serialize();
    seqResources.add(seqTsFileResource3);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(2, result.get(0).getSeqFiles().size());
    Assert.assertEquals(seqTsFileResource2, result.get(0).getSeqFiles().get(1));
  }

  @Test
  public void
      testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFilesWithUnclosedSeqFileAndInvalidCandidate()
          throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2 (invalid)
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqTsFileResource2.setStatus(TsFileResourceStatus.COMPACTION_CANDIDATE);
    seqResources.add(seqTsFileResource2);

    // seq file 3 (unclosed)
    // device: d2, time: [500, ]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    seqTsFileResource3.updateStartTime(new PlainDeviceID(COMPACTION_TEST_SG + ".d2"), 500);
    seqTsFileResource3.setStatusForTest(TsFileResourceStatus.UNCLOSED);

    seqTsFileResource3.serialize();
    seqResources.add(seqTsFileResource3);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [300, 400]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 300, 400);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(0, result.size());
  }

  @Test
  public void
      testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFilesWithInvalidCandidate1()
          throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3 (invalid)
    // device: d2, time: [500, 600]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource3.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource3, "d2", true, 500, 600);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource3.serialize();
    seqTsFileResource3.setStatus(TsFileResourceStatus.COMPACTION_CANDIDATE);
    seqResources.add(seqTsFileResource3);

    // seq file 4
    // device: d2, time: [900, 1000]
    TsFileResource seqTsFileResource4 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource4.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource4, "d2", true, 900, 1000);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource4.serialize();
    seqResources.add(seqTsFileResource4);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [700, 800]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 700, 800);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(2, result.get(0).getSeqFiles().size());
    Assert.assertEquals(seqTsFileResource1, result.get(0).getSeqFiles().get(0));
    Assert.assertEquals(seqTsFileResource4, result.get(0).getSeqFiles().get(1));
  }

  @Test
  public void
      testUnSeqFileOverlapWithSeqFilesButOneDeviceNotExistInOverlapSeqFilesWithInvalidCandidate2()
          throws IOException, IllegalPathException {
    // seq file 1
    // device: d1, time: [150, 400]
    TsFileResource seqTsFileResource1 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource1, "d1", true, 150, 400);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource1.serialize();
    seqResources.add(seqTsFileResource1);

    // seq file 2
    // device: d2, time: [100, 200]
    TsFileResource seqTsFileResource2 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource2.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource2, "d2", true, 100, 200);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource2.serialize();
    seqResources.add(seqTsFileResource2);

    // seq file 3 (invalid)
    // device: d2, time: [500, 600]
    TsFileResource seqTsFileResource3 = createEmptyFileAndResource(true);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(seqTsFileResource3.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, seqTsFileResource3, "d2", true, 500, 600);
      tsFileIOWriter.endFile();
    }
    seqTsFileResource3.serialize();
    seqTsFileResource3.setStatus(TsFileResourceStatus.COMPACTION_CANDIDATE);
    seqResources.add(seqTsFileResource3);

    // unSeq file 1
    // device: d1, time: [100, 300]
    // device: d2, time: [700, 800]
    TsFileResource unSeqTsFileResource1 = createEmptyFileAndResource(false, 1);
    try (TsFileIOWriter tsFileIOWriter = new TsFileIOWriter(unSeqTsFileResource1.getTsFile())) {
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d1", false, 100, 300);
      createSimpleDevice(tsFileIOWriter, unSeqTsFileResource1, "d2", false, 700, 800);
      tsFileIOWriter.endFile();
    }
    unSeqTsFileResource1.serialize();
    unseqResources.add(unSeqTsFileResource1);

    tsFileManager.addAll(seqResources, true);
    tsFileManager.addAll(unseqResources, false);
    RewriteCrossSpaceCompactionSelector selector =
        new RewriteCrossSpaceCompactionSelector(
            COMPACTION_TEST_SG, "0", 0, tsFileManager, new CompactionScheduleContext());

    List<CrossCompactionTaskResource> result =
        selector.selectCrossSpaceTask(seqResources, unseqResources);
    Assert.assertEquals(0, result.size());
  }

  public void createSimpleDevice(
      TsFileIOWriter fileWriter,
      TsFileResource resource,
      String deviceName,
      boolean isSeq,
      long startTime,
      long endTime)
      throws IOException, IllegalPathException {
    IDeviceID deviceId = new PlainDeviceID(COMPACTION_TEST_SG + "." + deviceName);

    fileWriter.startChunkGroup(deviceId);

    List<TSDataType> dataTypes = TsFileGeneratorUtils.createDataType(1);
    List<TSEncoding> encodingTypes = TsFileGeneratorUtils.createEncodingType(1);
    List<CompressionType> compressionTypes = TsFileGeneratorUtils.createCompressionType(1);
    List<PartialPath> timeSeriesPaths = new ArrayList<>();
    timeSeriesPaths.add(
        new MeasurementPath(((PlainDeviceID) deviceId).toStringID() + ".s1", dataTypes.get(0)));

    List<IChunkWriter> chunkWriters =
        TsFileGeneratorUtils.createChunkWriter(
            timeSeriesPaths, dataTypes, encodingTypes, compressionTypes, false);
    IChunkWriter chunkWriter = chunkWriters.get(0);
    List<TimeRange> pages = new ArrayList<>();
    pages.add(new TimeRange(startTime, endTime));
    TsFileGeneratorUtils.writeOneNonAlignedPage((ChunkWriterImpl) chunkWriter, pages, isSeq);

    fileWriter.endChunkGroup();

    resource.updateStartTime(deviceId, startTime);
    resource.updateEndTime(deviceId, endTime);
  }
}
