package picard.illumina.parser;

import htsjdk.samtools.util.IOUtil;
import picard.PicardException;
import picard.illumina.parser.fakers.BciFileFaker;
import picard.illumina.parser.fakers.FileFaker;
import picard.illumina.parser.fakers.FilterFileFaker;
import picard.illumina.parser.fakers.MultiTileLocsFileFaker;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * For file types for which there is one file per lane, with fixed record size, and all the tiles in it,
 * so the s_<lane>.bci file can be used to figure out where each tile starts and ends.
 */
public abstract class MultiTileFileUtil<OUTPUT_RECORD extends IlluminaData> extends ParameterizedFileUtil {
    protected final File bci;
    protected TileIndex tileIndex;
    protected File dataFile;

    MultiTileFileUtil(final String extension, final File base, final File bciDir, final FileFaker fileFaker,
                      final int lane) {
        super(false, extension, base, fileFaker, lane);
        bci = new File(bciDir, "s_" + lane + ".bci");
        if (bci.exists()) {
            tileIndex = new TileIndex(bci);
        } else {
            tileIndex = null;
        }
        final File[] filesMatchingRegexp = IOUtil.getFilesMatchingRegexp(base, matchPattern);
        if (filesMatchingRegexp == null || filesMatchingRegexp.length == 0) {
            dataFile = null;
        } else if (filesMatchingRegexp.length == 1) {
            dataFile = filesMatchingRegexp[0];
        } else {
            throw new PicardException("More than one filter file found in " + base.getAbsolutePath());
        }
    }

    @Override
    public boolean filesAvailable() {
        return tileIndex != null && dataFile != null && dataFile.exists();
    }

    @Override
    public List<Integer> getTiles() {
        if (tileIndex == null) {
            return Collections.emptyList();
        }
        return tileIndex.getTiles();
    }

    /**
     * expectedCycles are not checked in this implementation.
     */
    @Override
    public List<String> verify(final List<Integer> expectedTiles, final int[] expectedCycles) {
        if (tileIndex == null) {
            return Collections.singletonList("Tile index(" + bci.getAbsolutePath() + ") does not exist!");
        }
        return tileIndex.verify(expectedTiles);
    }

    @Override
    public List<String> fakeFiles(final List<Integer> expectedTiles, final int[] expectedCycles,
                                  final IlluminaFileUtil.SupportedIlluminaFormat format) {
        //we need to fake a bci file for the tile index
        final BciFileFaker bciFileFaker = new BciFileFaker();
        try {
            bciFileFaker.fakeBciFile(bci, expectedTiles);
            tileIndex = new TileIndex(bci);
            faker.fakeFile(base, expectedTiles, lane, extension);
            final File[] filesMatchingRegexp = IOUtil.getFilesMatchingRegexp(base, matchPattern);
            if (filesMatchingRegexp == null || filesMatchingRegexp.length == 0) {
                dataFile = null;
            } else if (filesMatchingRegexp.length == 1) {
                dataFile = filesMatchingRegexp[0];
            } else {
                throw new PicardException("More than one filter file found in " + base.getAbsolutePath());
            }
        } catch (final IOException e) {
            return Collections.singletonList("Could not create tile index file: " + bci.getAbsolutePath());
        }
        return tileIndex.verify(expectedTiles);
    }

    abstract IlluminaParser<OUTPUT_RECORD> makeParser(List<Integer> requestedTiles);

}

class MultiTileFilterFileUtil extends MultiTileFileUtil<PfData> {

    /**
     * @param basecallLaneDir location of .filter file and also .bci file
     */
    MultiTileFilterFileUtil(final File basecallLaneDir, final int lane) {
        super(".filter", basecallLaneDir, basecallLaneDir, new FilterFileFaker(), lane);
    }

    @Override
    IlluminaParser<PfData> makeParser(final List<Integer> requestedTiles) {
        return new MultiTileFilterParser(tileIndex, requestedTiles, dataFile);
    }
}

class MultiTileLocsFileUtil extends MultiTileFileUtil<PositionalData> {

    MultiTileLocsFileUtil(final File basecallLaneDir, final File bciDir, final int lane) {
        super(".locs", basecallLaneDir, bciDir, new MultiTileLocsFileFaker(), lane);
    }

    @Override
    IlluminaParser<PositionalData> makeParser(final List<Integer> requestedTiles) {
        return new MultiTileLocsParser(tileIndex, requestedTiles, dataFile, lane);
    }
}

