/**
  * get meta, that is correct target of timestamp if present
  *
  * @param timestamp
  */
 private ChunkMeta getChunkMeta(BTreeMap<Integer, ChunkMeta> chunkMetaMap, int timestamp) {
   int timestamp_year = TimeUtil.roundLowerYear(timestamp);
   int timestamp_next_year = TimeUtil.roundNextYear(timestamp);
   Integer key = chunkMetaMap.ceilingKey(timestamp_year);
   if (key == null) {
     return null;
   }
   if (timestamp_next_year <= key) {
     return null;
   }
   ChunkMeta chunkMeta = chunkMetaMap.get(key);
   throwNull(chunkMeta);
   return chunkMeta;
 }
  private void insertIntoOneChunk(
      BTreeMap<Integer, ChunkMeta> chunkMetaMap,
      BTreeMap<Integer, Chunk> chunkMap,
      ArrayList<DataEntry> entryList) {
    // int timestamp_chunk = TimeConverter.roundLowerYear(entryList.get(0).timestamp);
    int timestamp_next_year = TimeUtil.roundNextYear(entryList.get(0).timestamp);
    if (timestamp_next_year <= entryList.get(entryList.size() - 1).timestamp) {
      throw new RuntimeException("data of more than one chunk");
    }
    // ChunkMeta oldChunkMeta = chunkMetaMap.get(timestamp_chunk);
    ChunkMeta oldChunkMeta = getChunkMeta(chunkMetaMap, entryList.get(0).timestamp);
    if (oldChunkMeta == null) {
      insertChunk(chunkMetaMap, chunkMap, Chunk.of(entryList));
    } else {
      Chunk oldChunk = chunkMap.get(oldChunkMeta.firstTimestamp);
      Iterator<DataEntry> oldIt = Arrays.stream(oldChunk.data).iterator();
      Iterator<DataEntry> newIt = entryList.iterator();
      ArrayList<DataEntry> resultList = new ArrayList<DataEntry>();

      DataEntry old_curr = oldIt.hasNext() ? oldIt.next() : null;
      DataEntry new_curr = newIt.hasNext() ? newIt.next() : null;

      while (old_curr != null || new_curr != null) {
        if (old_curr != null) {
          if (new_curr != null) {
            if (old_curr.timestamp == new_curr.timestamp) { // overwrite old data with new data
              resultList.add(new_curr);
              old_curr = oldIt.hasNext() ? oldIt.next() : null;
              new_curr = newIt.hasNext() ? newIt.next() : null;
            } else if (old_curr.timestamp < new_curr.timestamp) {
              resultList.add(old_curr);
              old_curr = oldIt.hasNext() ? oldIt.next() : null;
            } else {
              resultList.add(new_curr);
              new_curr = newIt.hasNext() ? newIt.next() : null;
            }
          } else {
            resultList.add(old_curr);
            old_curr = oldIt.hasNext() ? oldIt.next() : null;
          }
        } else {
          resultList.add(new_curr);
          new_curr = newIt.hasNext() ? newIt.next() : null;
        }
      }

      removeChunk(chunkMetaMap, chunkMap, oldChunkMeta);
      insertChunk(chunkMetaMap, chunkMap, Chunk.of(resultList));
    }
  }
  public void insertSensorData(String stationName, String sensorName, DataEntry[] data) {
    // log.info("streamDB insert data "+stationName+" "+sensorName+" "+data.length);
    throwNull(stationName);
    throwNull(sensorName);
    throwNull(data);
    if (data.length == 0) {
      log.warn("no data to insert");
      return;
    }
    SensorMeta sensorMeta = getSensorMeta(stationName, sensorName, true);
    BTreeMap<Integer, ChunkMeta> chunkMetaMap = getSensorChunkMetaMap(sensorMeta);
    BTreeMap<Integer, Chunk> chunkMap = getSensorChunkMap(sensorMeta);

    int timestamp_next_year = Integer.MIN_VALUE;
    ArrayList<DataEntry> entryList = new ArrayList<DataEntry>(data.length);
    int prevTimestamp = -1;
    for (DataEntry entry : data) {
      if (entry.timestamp <= prevTimestamp) {
        throw new RuntimeException(
            "not ordered timestamps "
                + TimeUtil.oleMinutesToText(prevTimestamp)
                + "  "
                + TimeUtil.oleMinutesToText(entry.timestamp)
                + "   "
                + entry.value
                + "  "
                + stationName
                + "/"
                + sensorName);
      }
      if (entry.timestamp < timestamp_next_year) {
        entryList.add(entry);
      } else {
        if (!entryList.isEmpty()) {
          insertIntoOneChunk(chunkMetaMap, chunkMap, entryList);
        }
        timestamp_next_year = TimeUtil.roundNextYear(entry.timestamp);
        entryList.clear();
        entryList.add(entry);
      }
      prevTimestamp = entry.timestamp;
    }
    if (!entryList.isEmpty()) {
      insertIntoOneChunk(chunkMetaMap, chunkMap, entryList);
    }
  }