content: /*************************************************************************
  *                                                                        *
  *  This file is part of the 20n/act project.                             *
  *  20n/act enables DNA prediction for synthetic biology/bioengineering.  *
  *  Copyright (C) 2017 20n Labs, Inc.                                     *
  *                                                                        *
  *  Please direct all queries to act@20n.com.                             *
  *                                                                        *
  *  This program is free software: you can redistribute it and/or modify  *
  *  it under the terms of the GNU General Public License as published by  *
  *  the Free Software Foundation, either version 3 of the License, or     *
  *  (at your option) any later version.                                   *
  *                                                                        *
  *  This program is distributed in the hope that it will be useful,       *
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
  *  GNU General Public License for more details.                          *
  *                                                                        *
  *  You should have received a copy of the GNU General Public License     *
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  *                                                                        *
  *************************************************************************/
  
  package com.act.lcms.v2.fullindex;
  
  import com.act.lcms.LCMSSpectrum;
  import com.act.utils.MockRocksDBAndHandles;
  import org.apache.commons.lang3.tuple.Pair;
  import org.junit.Before;
  import org.junit.Test;
  
  import java.nio.ByteBuffer;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertTrue;
  
  public class BuilderTest {
    public static final double FP_TOLERANCE = 0.000001;
  
    public static final double[] TIMES = { 1.0, 2.0, 3.0 };
    public static final double[][] MZS = {
        {100.000, 100.005, 100.010},
        {100.005, 100.010, 100.015},
        {100.010, 100.015, 100.020},
    };
    public static final double[][] INTENSITIES =  {
        {  1.0,   2.0,   3.0},
        { 10.0,  20.0,  30.0},
        {100.0, 200.0, 300.0},
    };
  
    public static final List<MZWindow> MZ_WINDOWS = new ArrayList<MZWindow>() {{
      add(new MZWindow(0, 100.000));
      add(new MZWindow(1, 100.010));
      add(new MZWindow(2, 100.020));
    }};
    public Map<Integer, MZWindow> windowIdsToWindows =
        new HashMap<Integer, MZWindow>() {{
          for (MZWindow window : MZ_WINDOWS){
            put(window.getIndex(), window);
          }
        }};
  
    public static MockRocksDBAndHandles<ColumnFamilies> populateTestDB() throws Exception {
      List<LCMSSpectrum> spectra = new ArrayList<> (TIMES.length);
      for (int i = 0; i < TIMES.length; i++) {
        List<Pair<Double, Double>> mzIntensities = new ArrayList<>();
        double totalIntensity = 0.0;
        for (int j = 0; j < MZS[i].length; j++) {
          mzIntensities.add(Pair.of(MZS[i][j], INTENSITIES[i][j]));
          totalIntensity += INTENSITIES[i][j];
        }
        spectra.add(new LCMSSpectrum(i, TIMES[i], "s", mzIntensities, null, null, null, i, totalIntensity));
      }
  
      MockRocksDBAndHandles<ColumnFamilies> testDB =
          new MockRocksDBAndHandles<>(ColumnFamilies.values());
      Builder builder = new Builder(testDB);
      builder.extractTriples(spectra.iterator(), MZ_WINDOWS);
      builder.writeWindowsToDB(MZ_WINDOWS);
      return testDB;
    }
  
    public MockRocksDBAndHandles<ColumnFamilies> fakeDB;
  
    @Before
    public void setup() throws Exception {
      fakeDB = populateTestDB();
    }
  
    @Test
    public void testExtractTriples() throws Exception {
      // Verify all TMzI triples are stored correctly.
      Map<Long, TMzI> deserializedTriples = new HashMap<>();
      assertEquals("All triples should have entries in the DB", 9,
          fakeDB.getFakeDB().get(ColumnFamilies.ID_TO_TRIPLE).size());
      for (Map.Entry<List<Byte>, byte[]> entry :
          fakeDB.getFakeDB().get(ColumnFamilies.ID_TO_TRIPLE).entrySet()) {
        Long id = ByteBuffer.wrap(fakeDB.byteListToArray(entry.getKey())).getLong();
        TMzI triple =
            TMzI.readNextFromByteBuffer(ByteBuffer.wrap(entry.getValue()));
        Float expectedTime = Double.valueOf(TIMES[id.intValue() / 3]).floatValue();
        Double expectedMZ = MZS[id.intValue() / 3][id.intValue() % 3];
        Float expectedIntensity = Double.valueOf(INTENSITIES[id.intValue() / 3][id.intValue() % 3]).floatValue();
  
        assertEquals("Time matches expected", expectedTime, triple.getTime(), FP_TOLERANCE); // No error expected
        assertEquals("M/z matches expected", expectedMZ, triple.getMz(), FP_TOLERANCE);
        assertEquals("Intensity matches expected", expectedIntensity, triple.getIntensity(), FP_TOLERANCE);
  
        deserializedTriples.put(id, triple);
      }
  
      for (Map.Entry<List<Byte>, byte[]> entry :
          fakeDB.getFakeDB().get(ColumnFamilies.WINDOW_ID_TO_TRIPLES).entrySet()) {
        int windowId = ByteBuffer.wrap(fakeDB.byteListToArray(entry.getKey())).getInt();
        MZWindow window = windowIdsToWindows.get(windowId);
  
        List<Long> tmziIds = new ArrayList<>(entry.getValue().length / Long.BYTES);
        ByteBuffer valBuffer = ByteBuffer.wrap(entry.getValue());
        while (valBuffer.hasRemaining()) {
          tmziIds.add(valBuffer.getLong());
        }
  
        for (Long tripleId : tmziIds) {
          TMzI triple = deserializedTriples.get(tripleId);
          assertTrue("Triple m/z falls within range of containing window",
              triple.getMz() >= window.getMin() && triple.getMz() <= window.getMax()
          );
        }
      }
  
      for (Map.Entry<List<Byte>, byte[]> entry :
          fakeDB.getFakeDB().get(ColumnFamilies.TIMEPOINT_TO_TRIPLES).entrySet()) {
        float time = ByteBuffer.wrap(fakeDB.byteListToArray(entry.getKey())).getFloat();
  
        List<Long> tmziIds = new ArrayList<>(entry.getValue().length / Long.BYTES);
        ByteBuffer valBuffer = ByteBuffer.wrap(entry.getValue());
        while (valBuffer.hasRemaining()) {
          tmziIds.add(valBuffer.getLong());
        }
  
        for (Long tripleId : tmziIds) {
          TMzI triple = deserializedTriples.get(tripleId);
          assertEquals("Triple time matches key time", time, triple.getTime(), FP_TOLERANCE);
        }
      }
    }
  
    @Test
    public void testAppendOrRealloc() throws Exception {
      ByteBuffer dest = ByteBuffer.allocate(4);
      assertEquals("Initial buffer capacity matches expected", 4, dest.capacity());
      dest = Utils.appendOrRealloc(dest, ByteBuffer.wrap(new byte[] {'a', 'b', 'c', 'd'})); // No need to flip w/ wrap().
      assertEquals("Post-append (fits) buffer capacity matches expected", 4, dest.capacity());
      assertEquals("Post-append (fits) buffer position matches expected", 4, dest.position());
      dest = Utils.appendOrRealloc(dest, ByteBuffer.wrap(new byte[] {'e'}));
      assertEquals("Post-append (too large) buffer capacity has doubled", 8, dest.capacity());
      assertEquals("Post-append (too large) buffer position matches expected", 5, dest.position());
      dest = Utils.appendOrRealloc(dest, ByteBuffer.wrap(new byte[] {'f', 'g', 'h'}));
      assertEquals("Post-append (fits) buffer capacity matches expected", 8, dest.capacity());
      assertEquals("Post-append (fits) buffer position matches expected", 8, dest.position());
      dest = Utils.appendOrRealloc(dest, ByteBuffer.wrap(new byte[] {'i'}));
      assertEquals("Post-append (too large) buffer capacity has doubled", 16, dest.capacity());
      assertEquals("Post-append (too large) buffer position matches expected", 9, dest.position());
  
    }
  }
  