/*
 * Copyright (c) 2007-2011 by The Broad Institute of MIT and Harvard.  All Rights Reserved.
 *
 * 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS." THE BROAD AND MIT MAKE NO REPRESENTATIONS OR
 * WARRANTES OF ANY KIND CONCERNING THE SOFTWARE, EXPRESS OR IMPLIED, INCLUDING,
 * WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER
 * OR NOT DISCOVERABLE.  IN NO EVENT SHALL THE BROAD OR MIT, OR THEIR RESPECTIVE
 * TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, AND AFFILIATES BE LIABLE FOR ANY DAMAGES
 * OF ANY KIND, INCLUDING, WITHOUT LIMITATION, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
 * ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER
 * THE BROAD OR MIT SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT
 * SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING.
 */

package org.broad.igv.bbfile;

import org.apache.log4j.Logger;
import htsjdk.tribble.util.LittleEndianInputStream;

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

/**
 * Created by IntelliJ IDEA.
 * User: Owner
 * Date: May 3, 2010
 * Time: 12:32:17 AM
 * To change this template use File | Settings | File Templates.
 */
public class BigWigSection {

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

    private boolean isLowToHigh;       // byte order is low to high if true; else high to low
    private LittleEndianInputStream lbdis;   // input stream reader for low to high byte ordered data
    private DataInputStream dis;      // input stream reader for high to low byte ordered data

    private RPTreeLeafNodeItem leafHitItem;    // leaf item defines chromosome region and file data location
    private int sectionDataSize;       // byte size of decompressed data for this section
    private HashMap<Integer, String> chromosomeMap; // map of chromosome ID's and corresponding names
    private BigWigSectionHeader wigSectionHeader;  // wig section header

    /*
    *   Constructor for a BigWig data section which includes the section header
    *   and Wig data items.
    *
    *   Parameters:
    *       sectionBuffer - buffer contains decompressed Wig section header + data
    *       sectionIndex - wig section index for leaf data block
    *       chromIDTree - B+ chromosome index tree returns chromosome names for ID's
    *       isLowToHigh - if true, data byte order is low to high ; else is high to low
    *       leafHitItem - contains leaf node information for testing against selection region
    *
    * */
    public BigWigSection(byte[] sectionBuffer, HashMap<Integer, String> chromosomeMap,
                         boolean isLowToHigh, RPTreeLeafNodeItem leafHitItem){

        this.chromosomeMap =  chromosomeMap;
        this.isLowToHigh = isLowToHigh;
        this.leafHitItem = leafHitItem;

        // wrap the Wig section buffer as an input stream and get the section header
        // Note: A RuntimeException is thrown if header is not read properly
        if(this.isLowToHigh) {
            lbdis = new LittleEndianInputStream(new ByteArrayInputStream(sectionBuffer));
            wigSectionHeader = new BigWigSectionHeader(lbdis);

        }
        else  {
            dis = new DataInputStream(new ByteArrayInputStream(sectionBuffer));
            wigSectionHeader = new BigWigSectionHeader(dis);
        }

        // check for valid Wig item type
        if(wigSectionHeader.getItemType() == BigWigSectionHeader.WigItemType.Unknown){
            throw new RuntimeException("Read error on wig section leaf index ");
        }

        // include header in data segment size accounting
        sectionDataSize = wigSectionHeader.SECTION_HEADER_SIZE;

        // use method getSectionData to extract section data
    }

    /*
    *   Method returns the if the Wig items defined in this section are valid.
    *
    *   Note: Use BigWigSectionHeader to obtain more information on
    *       Wig section data specifications.
    *
    *   Returns:
    *       Specifies if Wig section has a valid data item type.
    * */
    public boolean isValidSectionType(){
        return wigSectionHeader.IsValidType();
    }

    /*
    *   Method returns the Wig Section Header
    *
    *   Returns:
    *       Wig section header
    * */
    public int getItemCount() {
        return wigSectionHeader.getItemCount();
    }

    /*
    *   Method returns the Wig Section Header
    *
    *   Returns:
    *       Wig section header
    * */
    public BigWigSectionHeader getSectionHeader() {
        return wigSectionHeader;
    }

    /*
    *   Method returns the number bytes of decompressed data in this section.
    *
    *   Returns:
    *       Number of uncompressed bytes read for the Wig data section
    * */
    public int getSectionDataSize() {
        return sectionDataSize;
    }

    /*
    *   Method reads Wig data items within the decompressed block buffer for the selection region.
    *
    *   Parameters:
    *       selectionRegion - chromosome selection region for item extraction
    *       contained - indicates select region must be contained in value region
    *           if true, else may intersect selection region for extraction
    *
    *   Returns:
    *     Size in bytes for the wig data section.
    *     Items read in the wig segment data block are added to the wig item list .
    *
     *   Note: Unlike ZoomLevel and BigBed formats, the Wig Section data block header contains
    *   an item count used to determine the end of data read.
    * */
    public int getSectionData(RPChromosomeRegion selectionRegion, boolean contained,
                              ArrayList<WigItem> wigItemList) {

        // get the section's data item specifications
        // Note: A RuntimeException is thrown if wig section header is not read properly
        int chromID =  wigSectionHeader.getChromID();
        String chromosome = chromosomeMap.get(chromID);
        int itemCount = wigSectionHeader.getItemCount();
        int chromStart = wigSectionHeader.getChromosomeStart();
        int chromEnd = wigSectionHeader.getChromosomeEnd();
        int itemStep = wigSectionHeader.getItemStep();
        int itemSpan =  wigSectionHeader.getItemSpan();
        int itemIndex = 0;
        int startBase = 0;
        int endBase = 0;
        float value = 0.0f;

        // find Wig data type - BBFile Table J item type
        BigWigSectionHeader.WigItemType itemType = wigSectionHeader.getItemType();

        // check if all leaf items are selection hits
        RPChromosomeRegion itemRegion = new RPChromosomeRegion(chromID, chromStart,
                            chromID, chromEnd);
        int leafHitValue = itemRegion.compareRegions(selectionRegion);


        // extract Wig data records
        // Note: the buffer input stream is positioned past section header
        try {
            for(int index = 0; index < itemCount; ++index) {
                ++itemIndex;
                if(isLowToHigh){
                    if(itemType == BigWigSectionHeader.WigItemType.FixedStep){
                        startBase = chromStart;
                        endBase = startBase + itemSpan;
                        value = lbdis.readFloat();
                        chromStart = startBase + itemStep;
                        sectionDataSize += BigWigSectionHeader.FIXEDSTEP_ITEM_SIZE;
                    }
                    else if(itemType == BigWigSectionHeader.WigItemType.VarStep){

                        startBase = lbdis.readInt();
                        endBase = startBase + itemSpan;
                        value = lbdis.readFloat();
                        sectionDataSize += BigWigSectionHeader.VARSTEP_ITEM_SIZE;
                    }
                    else if(itemType == BigWigSectionHeader.WigItemType.BedGraph){
                        startBase = lbdis.readInt();
                        endBase = lbdis.readInt();
                        value = lbdis.readFloat();
                        sectionDataSize += BigWigSectionHeader.BEDGRAPH_ITEM_SIZE;
                    }
                }
                else {  // byte order is high to low
                    if(itemType == BigWigSectionHeader.WigItemType.FixedStep){
                        startBase = chromStart;
                        endBase = startBase + itemSpan;
                        value = dis.readFloat();
                        chromStart = startBase + itemStep;
                        sectionDataSize += BigWigSectionHeader.FIXEDSTEP_ITEM_SIZE;
                    }
                    else if(itemType == BigWigSectionHeader.WigItemType.VarStep){
                        startBase = dis.readInt();
                        endBase = startBase + itemSpan;
                        value = dis.readFloat();
                        sectionDataSize += BigWigSectionHeader.VARSTEP_ITEM_SIZE;
                    }
                    else if(itemType == BigWigSectionHeader.WigItemType.BedGraph){
                        startBase = dis.readInt();
                        endBase = dis.readInt();
                        value = dis.readFloat();
                        sectionDataSize += BigWigSectionHeader.BEDGRAPH_ITEM_SIZE;
                    }
                }

                // contained leaf region items are always added - otherwise test conditions
                if(leafHitValue == 0) {
                    WigItem bbItem = new WigItem(itemIndex, chromosome, startBase, endBase, value);
                    wigItemList.add(bbItem);
                }
                else {
                    itemRegion = new RPChromosomeRegion(chromID, startBase, chromID, endBase);
                    int itemHitValue = itemRegion.compareRegions(selectionRegion);

                    // hitValue < 2 needed for intersection; hitValue < 1 needed for contained = true
                    if(itemHitValue == 0 || !contained && Math.abs(itemHitValue) < 2) {
                        WigItem bbItem = new WigItem(itemIndex, chromosome, startBase, endBase, value);
                        wigItemList.add(bbItem);
                    }
                }

            }

        }catch(IOException ex) {
            log.error("Read error for Wig section item " + itemIndex);
            throw new RuntimeException("Read error for Wig section item " + itemIndex);
        }

        return sectionDataSize;
    }

    /*
    *   Method prints out the data items for this Wig section.
    * */
    public void print() {
        log.debug("Wig section for leaf item  has a data size = " + sectionDataSize);
        wigSectionHeader.print();
    }

}
