/*
 * Copyright (c) 2007-2012 The Broad Institute, Inc.
 * SOFTWARE COPYRIGHT NOTICE
 * This software and its documentation are the copyright of the Broad Institute, Inc. All rights are reserved.
 *
 * This software is supplied without any warranty or guaranteed support whatsoever. The Broad Institute is not responsible for its use, misuse, or functionality.
 *
 * This software is licensed under the terms of the GNU Lesser General Public License (LGPL),
 * Version 2.1 which is available at http://www.opensource.org/licenses/lgpl-2.1.php.
 */

package org.broad.igv.bbfile;

import htsjdk.samtools.seekablestream.SeekableStream;
import org.apache.log4j.Logger;
import org.broad.igv.util.CompressionUtils;

import java.util.ArrayList;
import java.io.IOException;
import java.util.HashMap;

/**
 * Created by IntelliJ IDEA.
 * User: martind
 * Date: Apr 16, 2010
 * Time: 10:32:51 AM
 * To change this template use File | Settings | File Templates.
 */
public class BigWigDataBlock {

    private static Logger log = Logger.getLogger(BigWigDataBlock.class);

    // BigWig data types sizes
    final int FIXED_STEP_ITEM_SIZE = 4;
    final int VAR_STEP_ITEM_SIZE = 8;
    final int BED_GRAPH_ITEM_SIZE = 12;

    // Bed data block access variables   - for reading in bed records from a file
    private long fileOffset;       // Wig data block file offset
    private long leafDataSize;     // byte size for data block specified in the R+ leaf
    private boolean isLowToHigh;   // if true, data is low to high byte order; else high to low

    // defines the bigWig data source
    private HashMap<Integer, String> chromosomeMap;  // map of chromosome ID's and corresponding names
    private RPTreeLeafNodeItem leafHitItem;   // R+ leaf item containing data block location

    // uncompressed byte stream buffer and readers
    private byte[] wigBuffer;      // buffer containing leaf block data uncompressed
    private int remDataSize;       // number of uncompressed data bytes not extracted

    // Wig data extraction members
    private ArrayList<WigItem> wigItemList;  // array of Wig section items

    /*
    *   Constructor for Wig data block reader.
    *
    *   Parameters:
    *       fis - file input stream handle
    *       leafHitItem - R+ tree leaf hit item containing data block file location and hit status
    *       chromIDTree - B+ chromosome index tree returns chromosome ID's for names
    *       isLowToHigh - byte order is low to high if true; else high to low
    *       uncompressBufSize - byte size for decompression buffer; else 0 for uncompressed
    *
    * */
    public BigWigDataBlock(SeekableStream fis, RPTreeLeafNodeItem leafHitItem,
                           HashMap<Integer, String> chromosomeMap, boolean isLowToHigh, int uncompressBufSize){
        this.leafHitItem = leafHitItem;
        this.chromosomeMap = chromosomeMap;
        this.isLowToHigh = isLowToHigh;

        fileOffset = this.leafHitItem.getDataOffset();
        leafDataSize = this.leafHitItem.geDataSize();
        byte[] buffer = new byte[(int) leafDataSize];

        // read Wig data block into a buffer
        try {
            fis.seek(fileOffset);
            fis.readFully(buffer);

            // decompress if necessary - the buffer size is 0 for uncompressed data
            // Note:  BBFile Table C specifies a decompression buffer size
            if(uncompressBufSize > 0)
                wigBuffer = (new CompressionUtils()).decompress(buffer, uncompressBufSize);
            else
                wigBuffer = buffer;    // use uncompressed read buffer directly
        }catch(IOException ex) {
            log.error("Error reading Wig section for leaf item ", ex);
            String error = String.format("Error reading Wig section for leaf item %d\n");
            throw new RuntimeException(error, ex);
        }

        // initialize unread data size
        remDataSize = wigBuffer.length;

        // use getWigData to extract data block items
    }

    /*
    *   Method reads all Wig data sections within the decompressed block buffer
    *   and returns those items in the chromosome selection region.
    *
    *   Parameters:
    *       selectionRegion - chromosome region for selecting Wig values
   *       contained - indicates selected data must be contained in selection region
    *           if true, else may intersect selection region
    *
    *   Returns:
    *      Wig sections in selected from the data block; else null for none selected.
    *
    * */
    public ArrayList<WigItem> getWigData(RPChromosomeRegion selectionRegion,
                                         boolean contained){

        wigItemList = new ArrayList<WigItem>();

        for(int index = 0; remDataSize > 0; ++index) {

            // extract items in the Wig data section
            // Note: A RuntimeException is thrown if wig section is not read properly
            BigWigSection wigSection = new BigWigSection(wigBuffer, chromosomeMap, isLowToHigh, leafHitItem);

            // get wig section items and section bytes read
            int sectionBytes = wigSection.getSectionData(selectionRegion, contained, wigItemList);

            // adjust remaining data block size
            remDataSize -= sectionBytes;
        }

        return wigItemList;
    }

    public void print() {

        log.debug("Wig section data referenced by leaf item ");

        for(int index = 0; index <= wigItemList.size(); ++index) {
            // BigWig sections print themselves
            wigItemList.get(index).print();
        }
    }


}
