package nxm.sys.test.junit.lib;

import java.util.ArrayList;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import nxm.sys.intr.File;
import nxm.sys.lib.Args;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.DbfFile;
import nxm.sys.lib.Midas;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Table;
import nxm.sys.test.junit.NMTestCase;

/**
 * Test class for DbfFiles
 * @author Kevin Esterheld
 * @since NeXtMidas 3.9.0
 */
public class DbfFileTest extends NMTestCase {

  private boolean isGradleBuild = false;
  private Midas M = getMidasContext();

  /** public constructor to allow running this test case directly with JUnit. */
  public DbfFileTest (String methodName) {
    this(methodName, null);
  }

  /**
   * This constructor is required by NeXtMidas Unit Testing framework.
   * @param methodName The name of the method being called.
   * @param midas The midas context to use when running the tests.
   */
  public DbfFileTest (String methodName, Midas midas) {
    super(methodName, midas);
    M = (midas!=null) ? midas : Shell.getMidasContext();
    isGradleBuild = Shell.isGradleBuild();
  }

  private DbfFile dbf = null;

  private DbfFile dbfRam = null;

  private double numRows = 246.0;

  private int recordsDefCount = 7;

  /**
   * runs before each testcase.
   */
  @Before
  public void setUp () {
    if (isGradleBuild) {
      M.warning("nxm.sys.junit.lib.IOResourceTest skipping all tests because setup fails in Gradle build");
      return;
    }
    // Copy the file into memory for writing, using this prevents creating file or changing the original. Everything is
    // done in memory.
    Args args = Args.parseCommand(getMidas(),
        "file copy/f "+urlSysTest+"worldtestfile.dbf P1=worldtestfile2.dbf{aux=ram}");
    File file = (File) Shell.loadCommand(getMidas(), args);
    Shell.execCommand(getMidas(), file);
    // Open the in memory file for input and output.
    dbfRam = new DbfFile(getMidasContext(), "worldtestfile2.dbf{aux=ram}");
    dbfRam.setFlags(DbfFile.INOUT);
    dbfRam.open();
    // Open the original file for input only. Can be used for asserts and for tests that do not need to write.
    dbf = new DbfFile(getMidasContext(), urlSysTest+"worldtestfile.dbf");
    dbf.setFlags(DataFile.INPUT);
    dbf.open();
  }

  /**
   * Tests that the right number of rows is returned.
   */
  @Test
  public void testGetNumberOfRows () {
    if (isGradleBuild) return;
    assertEquals("The number of rows should be " + numRows, numRows, dbf.getNumberOfRows());
  }

  /**
   * Tests that the right size is returned.
   */
  @Test
  public void testGetSize () {
    if (isGradleBuild) return;
    assertEquals("The size should be " + numRows, numRows, dbf.getSize());
  }

  /**
   * Test that the record count is correct
   */
  @Test
  public void testGetRecordDefCount () {
    if (isGradleBuild) return;
    assertEquals("The number of records defined should be " + recordsDefCount, dbf.getRecordDefCount(),
        recordsDefCount);
  }

  /**
   * Tests that the removing of data is done correctly. It tests removing one row or multiple rows and shifts any left
   * over rows to the spot of the removed rows.
   */
  public void testRemoveData () {
    if (isGradleBuild) return;
    // To test removing bytes, we want to put this into memory so lets copy this file.

    dbfRam.removeData(208);
    // close and reopen to be sure the data is good
    final int removeSingle = 245;
    final int removeMany = 25;
    final int countToRemove = 100;

    assertEquals(removeSingle, dbfRam.getSize());
    assertEquals("element at 245 is now at 244", dbf.listElements(removeSingle, 0, "0", 0),
        dbfRam.listElements(removeSingle - 1, 0, "0", 0));

    dbfRam.removeData(removeMany, countToRemove);
    assertEquals("The row at " + (removeMany + countToRemove) + " is now equal to the row at " + removeMany,
        dbf.getDataTable(removeMany + countToRemove), dbfRam.getDataTable(removeMany));
    assertEquals("the size should now be the original less the 1 removed less the many removed",
        dbf.getSize() - countToRemove - 1, dbfRam.getSize());
    dbfRam.removeData(100, -1);
    List<Table> tablesToBeShifted = new ArrayList<>();
    for (int i = 15; i < 25; i++) {
      tablesToBeShifted.add(dbfRam.getDataTable(i));
    }
    dbfRam.removeData(5, 10);
    for (int i = 5; i < 15; i++) {
      assertEquals("After shifting, all the tables should be the same", tablesToBeShifted.get(i - 5),
          dbfRam.getDataTable(i));
    }
  }

  /**
   * Tests setting data. This will overwrite the data in the row it is being inserted into.
   */
  @Test
  public void testSetData () {
    if (isGradleBuild) return;
    // This tests the replacing of data with a new table.
    Table tbl = new Table();
    tbl.addIfNotPresent("FIPS", "AA");
    tbl.addIfNotPresent("CODE", "BB");
    tbl.addIfNotPresent("CODE_3", "CCC");
    tbl.addIfNotPresent("NAME", "testing");
    tbl.addIfNotPresent("POPULATION", 0.0);
    tbl.addIfNotPresent("LONGITUDE", 0.0);
    tbl.addIfNotPresent("LATITUDE", 0.0);
    dbfRam.setData(0, tbl);
    // This tests the replacing of a single field in a record.
    dbfRam.setData(208, "NAME", "No Longer the US");
    assertEquals("Change the name from United States to 'No Longer the US'", "No Longer the US",
        dbfRam.getDataTable(208).get("NAME"));
  }

  /**
   * This will test inserting and shifting the rows. Thsi will not overwrite data in the row, rather shift all the other
   * rows to the next.
   */
  @Test
  public void testInsertData () {
    if (isGradleBuild) return;
    double size = dbfRam.getSize();
    Table tbl = dbfRam.getDataTable(25);
    dbfRam.insertData(25, dbfRam.getDataTable(0));
    Table tbl2 = dbfRam.getDataTable(26);
    assertEquals("The tables should be equal just relocated", tbl2, tbl);
    assertEquals("The tables should be equal as the first was inserted at the seconds location", dbfRam.getDataTable(0),
        dbfRam.getDataTable(25));
    assertEquals("The size should be increased by one", size + 1, dbfRam.getSize());
  }

  /**
   * this tests the setting or changing of recrod definitions.
   */
  @Test
  public void testSetRecordDefs () {
    if (isGradleBuild) return;

    dbfRam.setRecordDefs(new Table("{TEST={NAME=\"TEST\",FORMAT=\"1A\",TYPE=\"C\",LENGTH=2,NUMDEC=0},CODE={NAME=\"CODE\",FORMAT=\"1A\",TYPE=\"C\",LENGTH=2,NUMDEC=0},CODE_3={NAME=\"CODE_3\",FORMAT=\"1A\",TYPE=\"C\",LENGTH=3,NUMDEC=0},NAME={NAME=\"NAME\",FORMAT=\"7A\",TYPE=\"C\",LENGTH=50,NUMDEC=0},POPULATION={NAME=\"POPULATION\",FORMAT=\"SL\",TYPE=\"N\",LENGTH=10,NUMDEC=0},LONGITUDE={NAME=\"LONGITUDE\",FORMAT=\"SD\",TYPE=\"N\",LENGTH=8,NUMDEC=3},LATITUDE={NAME=\"LATITUDE\",FORMAT=\"SD\",TYPE=\"N\",LENGTH=7,NUMDEC=3}}"));
    Table tbl = new Table("{NAME=\"TEST\",FORMAT=\"1A\",TYPE=\"C\",LENGTH=2,NUMDEC=0}");
    // Use toString on the equals as I think the equals method does object equals instead of value equals
    assertEquals("These are the same string value", dbfRam.getRecordDefs().get("TEST").toString(), tbl.toString());
  }

  /**
   * this is the cleanup after each test.
   */
  @After
  public final void tearDown () {
    if (isGradleBuild) return;
    dbf.close();
    dbfRam.close();
    // remove from ram forced
    dbfRam.erase(true);
    // make sure it is removed
    assertFalse(dbfRam.exists());
  }
}