package picard.illumina.parser.readers;
import htsjdk.samtools.Defaults;
import htsjdk.samtools.util.BlockCompressedInputStream;
import htsjdk.samtools.util.IOUtil;
import picard.PicardException;
import picard.illumina.parser.BclData;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
class BaseBclReader {
private static final byte BASE_MASK = 0x0003;
private static final byte[] BASE_LOOKUP = new byte[]{'A', 'C', 'G', 'T'};
final InputStream[] streams;
final File[] streamFiles;
final int[] outputLengths;
private BclQualityEvaluationStrategy bclQualityEvaluationStrategy;
final int[] numClustersPerCycle;
final int cycles;
BaseBclReader(int[] outputLengths, BclQualityEvaluationStrategy bclQualityEvaluationStrategy) {
this.outputLengths = outputLengths;
this.bclQualityEvaluationStrategy = bclQualityEvaluationStrategy;
int cycles = 0;
for (final int outputLength : outputLengths) {
cycles += outputLength;
}
this.cycles = cycles;
this.streams = new InputStream[cycles];
this.streamFiles = new File[cycles];
this.numClustersPerCycle = new int[cycles];
}
BaseBclReader(int[] outputLengths) {
this.outputLengths = outputLengths;
int cycles = 0;
for (final int outputLength : outputLengths) {
cycles += outputLength;
}
this.cycles = cycles;
this.streams = new InputStream[cycles];
this.streamFiles = new File[cycles];
this.numClustersPerCycle = new int[cycles];
}
int getNumCycles() {
return cycles;
}
int getNumClusters() {
return numClustersPerCycle[0];
}
InputStream open(final File file, final boolean seekable, final boolean isGzip, final boolean isBgzf) throws IOException {
final String filePath = file.getAbsolutePath();
try {
// Open up a buffered stream to read from the file and optionally wrap it in a gzip stream if necessary
if (isBgzf) {
// Only BlockCompressedInputStreams can seek, and only if they are fed a SeekableStream.
return new BlockCompressedInputStream(IOUtil.maybeBufferedSeekableStream(file));
} else if (isGzip) {
if (seekable) {
throw new IllegalArgumentException(
String.format("Cannot create a seekable reader for gzip bcl: %s.", filePath)
);
}
return (IOUtil.maybeBufferInputStream(new GZIPInputStream(new FileInputStream(file), Defaults.BUFFER_SIZE / 2),
Defaults.BUFFER_SIZE / 2));
} else {
if (seekable) {
throw new IllegalArgumentException(
String.format("Cannot create a seekable reader for provided bcl: %s.", filePath)
);
}
return IOUtil.maybeBufferInputStream(new FileInputStream(file));
}
} catch (final FileNotFoundException fnfe) {
throw new PicardException("File not found: (" + filePath + ")", fnfe);
} catch (final IOException ioe) {
throw new PicardException("Error reading file: (" + filePath + ")", ioe);
}
}
void decodeBasecall(final BclData bclData, final int read, final int cycle, final int byteToDecode) {
if (byteToDecode == 0) {
bclData.bases[read][cycle] = (byte) '.';
bclData.qualities[read][cycle] = (byte) 2;
} else {
bclData.bases[read][cycle] = BASE_LOOKUP[byteToDecode & BASE_MASK];
bclData.qualities[read][cycle] = bclQualityEvaluationStrategy.reviseAndConditionallyLogQuality((byte) (byteToDecode >>> 2));
}
}
void decodeQualityBinnedBasecall(final BclData bclData, final int read, final int cycle, final int byteToDecode,
final CycleData cycleData) {
if (byteToDecode == 0) {
bclData.bases[read][cycle] = (byte) '.';
bclData.qualities[read][cycle] = 2;
} else {
bclData.bases[read][cycle] = BASE_LOOKUP[byteToDecode & BASE_MASK];
bclData.qualities[read][cycle] = cycleData.qualityBins[byteToDecode >>> 2];
}
}
class CycleData {
final short version;
final int headerSize;
final byte bitsPerBasecall;
final byte bitsPerQualityScore;
final int numberOfBins;
final byte[] qualityBins;
final int numTiles;
final TileData[] tileInfo;
final boolean pfExcluded;
CycleData(final short version, final int headerSize, final byte bitsPerBasecall,
final byte bitsPerQualityScore, final int numberOfBins, final byte[] qualityBins,
final int numTiles, final TileData[] tileInfo, final boolean pfExcluded) {
this.version = version;
this.headerSize = headerSize;
this.bitsPerBasecall = bitsPerBasecall;
this.bitsPerQualityScore = bitsPerQualityScore;
this.numberOfBins = numberOfBins;
this.qualityBins = qualityBins;
this.numTiles = numTiles;
this.tileInfo = tileInfo;
this.pfExcluded = pfExcluded;
}
}
class TileData {
final int tileNum;
final int numClustersInTile;
final int uncompressedBlockSize;
final int compressedBlockSize;
TileData(final int tileNum, final int numClustersInTile, final int uncompressedBlockSize,
final int compressedBlockSize) {
this.tileNum = tileNum;
this.numClustersInTile = numClustersInTile;
this.uncompressedBlockSize = uncompressedBlockSize;
this.compressedBlockSize = compressedBlockSize;
}
}
}