/*
 * Copyright (C) 2009-2010 Institute for Computational Biomedicine,
 *                    Weill Medical College of Cornell University
 *
 *  This file is part of the Goby IO API.
 *
 *     The Goby IO API is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     The Goby IO API 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 Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with the Goby IO API.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.campagnelab.goby.counts;

import org.apache.commons.io.FileUtils;
import org.campagnelab.goby.counts.compound.CompoundDataOutput;
import org.campagnelab.goby.counts.compound.CompoundFileWriter;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;

/**
 * Writes archives of counts information  for several sequences. CountsArchiveWriter leverages
 * the {@link org.campagnelab.goby.counts.compound.CompoundFileWriter}.
 *
 * @author Fabien Campagne
 *         Date: May 13, 2009
 *         Time: 7:06:57 PM
 */
public class CountsArchiveWriter implements Closeable {


    /**
     * Default counts archive extension.
     */
    public static final String COUNT_ARCHIVE_MODIFIER_DEFAULT = "counts";

    private final CompoundFileWriter compoundWriter;
    private CountsWriterI currentCountsWriterI;
    private String currentId;

    private ByteArrayOutputStream stream;
    private long totalBitsWritten;
    private int totalTransitions;
    private long totalBasesSeen;
    private long totalSitesSeen;
    private CountIndexBuilder indexBuilder = new CountIndexBuilder();
    private boolean verbose = false;

    /**
     * Initialize with a basename. Count information will be written to a file basename+".counts"
     *
     * @param basename Basename for the alignment/counts.
     * @throws IOException If an error occurs preparing the packaged counts.
     */
    public CountsArchiveWriter(final String basename) throws IOException {
        this(basename, COUNT_ARCHIVE_MODIFIER_DEFAULT);
    }

    /**
     * Initialize with a basename. Count information will be written to a file basename+"."+countArchiveModifier
     *
     * @param basename             Basename for the alignment/counts.
     * @param countArchiveModifier the extension to write the archive.
     * @throws IOException If an error occurs preparing the packaged counts.
     */
    public CountsArchiveWriter(final String basename, final String countArchiveModifier) throws IOException {
        final String physicalFilename = basename + "." + countArchiveModifier;
        final File tobedeleted = new File(physicalFilename);
        // delete before writing since the compound file writer supports appending.
        FileUtils.deleteQuietly(tobedeleted);
        compoundWriter = new CompoundFileWriter(physicalFilename);

    }

    /**
     * Obtain a countsWriter to write counts for a sequence. Identifier provides a means to keep
     * track of multiple sequences.
     *
     * @param referenceIndex index of the sequence for which counts need to be recorded.
     * @param identifier     Identifier of the sequence for which counts need to be recorded.
     * @return A ready CountWriter implementation.
     * @throws IOException If an error occurs.
     */
    public CountsWriterI newCountWriter(final int referenceIndex, final String identifier) throws IOException {
        stream = new ByteArrayOutputStream(100000);
        currentCountsWriterI = new CountsWriter(stream,1);
        currentId = Integer.toString(referenceIndex) + "," + identifier;
        return currentCountsWriterI;
    }

    /**
     * Obtain a countsWriter to write counts for a sequence. CountInfoIndex provides a means to
     * keep track of multiple sequences.
     *
     * @param countInfoIndex Identifier of the sequence for which counts need to be recorded.
     * @return A ready CountWriter implementation.
     * @throws IOException If an error occurs.
     */
    public CountsWriterI newCountWriter(final int countInfoIndex) throws IOException {
        return newCountWriter(countInfoIndex, String.valueOf(countInfoIndex));
    }

    /**
     * Return a count Writer to the counts archive. Count writers must be returned to the
     * archive after they have been populated with count information.
     *
     * @param writerI The countWriter being returned.
     * @throws IOException If an error occurs packaging the count information in the archive.
     */
    public void returnWriter(final CountsWriterI writerI) throws IOException {
        assert writerI == currentCountsWriterI : "You must return the current counts writer.";
        writerI.close();
        totalBitsWritten += writerI.getNumberOfBitsWritten();
        totalTransitions += writerI.getNumberOfTransitions();
        totalBasesSeen += writerI.getNumberOfBasesSeen();
        totalSitesSeen += writerI.getNumberOfSitesSeen();
        final byte[] bytes = stream.toByteArray();

        final CompoundDataOutput part = compoundWriter.addFile(currentId);
        part.write(bytes);
        part.close();
        compoundWriter.finishAddFile();

        final CompoundDataOutput indexPart = compoundWriter.addFile("#index:" + currentId);
        indexBuilder.buildIndex(bytes, indexPart);
        indexPart.close();
        compoundWriter.finishAddFile();
        currentId = null;
        currentCountsWriterI = null;
    }

    /**
     * Closes this archive.
     *
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        // a file whose name starts with # is special and not interpreted as a reference sequence index,id
        final CompoundDataOutput statsFile = compoundWriter.addFile("#stats");
        statsFile.writeUTF("totalBasesSeen");
        statsFile.writeLong(totalBasesSeen);
        statsFile.writeUTF("totalSitesSeen");
        statsFile.writeLong(totalSitesSeen);
        statsFile.writeUTF("END");
        statsFile.close();
        compoundWriter.finishAddFile();
        if (verbose) {
            System.out.println("Global statististics:%n");
            System.out.printf("Bits written: %d %n", totalBitsWritten);
            System.out.printf("Number of transitions: %d %n", totalTransitions);
            System.out.printf("Bits per transitions: %2.2g %n", ((double) (totalBitsWritten) / (double) totalTransitions));
        }
        compoundWriter.close();
    }

    /**
     * Set the verbose flag. When the flag is true, prints some statistics to the console upon close(). Otherwise, no
     * output to the console.
     *
     * @param verbose Verbose flag.
     */
    public void setVerbose(final boolean verbose) {
        this.verbose = verbose;
    }
}
