content: /*
   * The MIT License (MIT)
   *
   * Copyright (c) 2011-2020 Broad Institute, Aiden Lab, Rice University, Baylor College of Medicine
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   *  THE SOFTWARE.
   */
  
  package juicebox.matrix;
  
  import htsjdk.samtools.seekablestream.SeekableStream;
  import htsjdk.samtools.seekablestream.SeekableStreamFactory;
  import htsjdk.tribble.util.LittleEndianInputStream;
  import org.broad.igv.util.ObjectCache;
  import org.broad.igv.util.ParsingUtils;
  
  import java.io.BufferedInputStream;
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  
  /**
   * Matrix class backed by a file layed out in "block" format.
   * <p/>
   * File format:
   * Field	Type	Description
   * Magic number	Integer	Value should be 6515048, which when read as a zero delimited string is “hic”.
   * Version	Integer	The version number, currently =  1
   * Genome	String	The genome ID (e.g. “hg19”)
   * Chromosome 1	String	Name of first chromosome
   * Chromosome 2	String	Name of second chromosome
   * Bin size	Integer	The bin size in base-pairs
   * Lower bounds for scale	Float	5th percentile suggested
   * Upper bounds for scale	Float	95th percentile suggested
   * Number of rows	Integer	Number of rows in the matrix.  Rows correspond to chromosome 1.
   * Number of columns	Integer
   * Block size  Integer
   *
   * @author jrobinso
   *         Date: 8/8/12
   *         Time: 8:42 AM
   */
  public class DiskResidentBlockMatrix implements BasicMatrix {
  
      private final String path;
      private final ObjectCache<String, float[][]> blockDataCache = new ObjectCache<>(200);
      boolean isLoading = false;
      private String genome;
      private String chr1;
      private String chr2;
      private int binSize;
      private float lowerValue;
      private float upperValue;
      private int dim;
      private int blockSize;
      private int remSize;   // Dimension of last block
      private int arrayStartPosition;
      private int nFullBlocks;
  
      public DiskResidentBlockMatrix(String path) throws IOException {
          this.path = path;
          init();
      }
  
      public String getChr1() {
          return chr1;
      }
  
      @Override
      public float getEntry(int row, int col) {
  
          int blockRowIdx = row / blockSize;
          int blockColIdx = col / blockSize;
          String key = "row" + blockRowIdx + "_col" + blockColIdx;
          float[][] blockData = blockDataCache.get(key);
          if (blockData == null) {
              blockData = loadBlockData(blockRowIdx, blockColIdx);
              blockDataCache.put(key, blockData);
          }
  
          if (blockData == null) {
              return Float.NaN;
          } else {
              int rowRelative = row - blockRowIdx * blockSize;
              int colRelative = col - blockColIdx * blockSize;
              int totalRows = blockData.length;
              int totalCols = blockData[0].length;
              // TODO: note this should not happen, probably the bug in MatrixZoomData where there are too many columns.
              if (rowRelative < totalRows && colRelative < totalCols)
                  return blockData[rowRelative][colRelative];
              else return Float.NaN;
          }
  
      }
  
      private synchronized float[][] loadBlockData(int blockRowIdx, int blockColIdx) {
  
          String key = "row" + blockRowIdx + "_col" + blockColIdx;
          float[][] blockData = blockDataCache.get(key);
          if (blockData != null) return blockData;    // In case this was calculated in another thread
  
          SeekableStream is = null;
          try {
              is = SeekableStreamFactory.getInstance().getStreamFor(path);
  
              int pointsPerBlockRow = blockSize * dim;  // Applies to all but the last row
  
              int rowDim = blockRowIdx < nFullBlocks ? blockSize : remSize;
              int colDim = blockColIdx < nFullBlocks ? blockSize : remSize;
  
              int l1 = blockRowIdx * pointsPerBlockRow;
              int l2 = blockColIdx * blockSize * rowDim;
              long startFilePosition = arrayStartPosition + (l1 + l2) * 4L;
  
  
              int nDataPoints = rowDim * colDim;
              int nBytes = nDataPoints * 4;
              byte[] byteArray = new byte[nBytes];
  
              is.seek(startFilePosition);
              is.readFully(byteArray);
  
              ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
              LittleEndianInputStream les = new LittleEndianInputStream(bis);
  
  
              blockData = new float[rowDim][colDim];
  
              for (int r = 0; r < rowDim; r++) {
                  for (int c = 0; c < colDim; c++) {
                      float f = les.readFloat();
                      blockData[r][c] = f;
                  }
              }
  
              blockDataCache.put(key, blockData);
              return blockData;
  
          } catch (IOException e) {
              System.err.println("Error reading block data: " + blockRowIdx + "-" + blockColIdx + " " + e.getLocalizedMessage());
              return null;
          } finally {
              if (is != null)
                  try {
                      is.close();
                  } catch (IOException e) {
                      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                  }
          }
      }
  
  
      @Override
      public int getRowDimension() {
          return dim;  //To change body of implemented methods use File | Settings | File Templates.
      }
  
      @Override
      public int getColumnDimension() {
          return dim;  //To change body of implemented methods use File | Settings | File Templates.
      }
  
  
      public float getLowerValue() {
          return lowerValue;
      }
  
      public float getUpperValue() {
          return upperValue;
      }
  
      @Override
      public void setEntry(int i, int j, float corr) {
  
      }
  
      private void init() throws IOException {
          // Peak at file to determine version
          BufferedInputStream bis = null;
          try {
              InputStream is = ParsingUtils.openInputStream(path);
              bis = new BufferedInputStream(is);
              LittleEndianInputStream les = new LittleEndianInputStream(bis);
  
              int bytePosition = 0;
              int magic = les.readInt();    // <= 6515048
              bytePosition += 4;
  
              //if (magic != 6515048)      <= ERROR
              // Version number
              int version = les.readInt();
              bytePosition += 4;
  
              genome = les.readString();
              bytePosition += genome.length() + 1;
  
              chr1 = les.readString();
              bytePosition += chr1.length() + 1;
  
              chr2 = les.readString();
              bytePosition += chr2.length() + 1;
  
              binSize = les.readInt();
              bytePosition += 4;
  
              lowerValue = les.readFloat();
              bytePosition += 4;
  
              upperValue = les.readFloat();
              bytePosition += 4;
  
              int nRows = les.readInt();  // # rows, assuming square matrix
              bytePosition += 4;
  
              int nCols = les.readInt();
              bytePosition += 4;
  
              if (nRows == nCols) {
                  dim = nRows;
              } else {
                  throw new RuntimeException("Non-square matrices not supported");
              }
  
              blockSize = les.readInt();
              bytePosition += 4;
  
              nFullBlocks = dim / blockSize;
              remSize = dim - nFullBlocks * blockSize;
  
              this.arrayStartPosition = bytePosition;
  
  
          } finally {
              if (bis != null)
                  bis.close();
          }
      }
  
  }
  