/*
 * Copyright (c) 2007-2010 by The Broad Institute, Inc. and the Massachusetts Institute of Technology.
 * 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 WARRANTIES 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.tribble.index;

import org.apache.log4j.Logger;
import org.broad.tribble.util.LittleEndianInputStream;
import org.broad.tribble.util.LittleEndianOutputStream;

import java.io.*;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: jrobinso
 * Date: Jul 9, 2010
 * Time: 8:37:26 AM
 * To change this template use File | Settings | File Templates.
 */
public abstract class AbstractIndex implements Index {
    private static Logger log = Logger.getLogger(AbstractIndex.class);

    public static int VERSION = 3;


    private int version;
    String indexedFile;
    long indexedFileSize;
    long indexedFileTS;
    String indexedFileMD5 = "";
    int flags;
    Map<String, String> properties;
    protected LinkedHashMap<String, ChrIndex> chrIndeces;
    private static final int SEQUENCE_DICTIONARY_FLAG = 0x8000;


    public AbstractIndex() {
        this.version = VERSION;
        this.properties = new HashMap();
        chrIndeces = new LinkedHashMap();
    }

    public AbstractIndex(String featureFile) {
        this.version = VERSION; // <= is overriden when file is read
        this.properties = new HashMap();
        chrIndeces = new LinkedHashMap();
        this.indexedFile = featureFile;
        this.indexedFileSize = featureFile.length();
        // TODO -- get time stamp
        //this.indexedFileTS = featureFile.lastModified();
    }


    /**
     * check the current version against the version we read in
     *
     * @return true if we're up to date, false otherwise
     */
    public boolean isCurrentVersion() {
        return version == VERSION;
    }

    public void setMD5(String md5) {
        this.indexedFileMD5 = md5;
    }

    public boolean containsChromosome(String chr) {
        return chrIndeces.containsKey(chr);
    }

    protected abstract int getType();


    private void writeHeader(LittleEndianOutputStream dos) throws IOException {

        int magicNumber = 1480870228;   //  byte[]{'T', 'I', 'D', 'X'};

        dos.writeInt(magicNumber);
        dos.writeInt(getType());
        dos.writeInt(version);
        dos.writeString(indexedFile);
        dos.writeLong(indexedFileSize);
        dos.writeLong(indexedFileTS);
        dos.writeString(indexedFileMD5);
        dos.writeInt(flags);

        // Properties (Version 3 and later)
        dos.writeInt(properties.size());
        for (Map.Entry<String, String> prop : properties.entrySet()) {
            dos.writeString(prop.getKey());
            dos.writeString(prop.getValue());
        }
    }

    private void readHeader(LittleEndianInputStream dis) throws IOException {

        version = dis.readInt();
        indexedFile = dis.readString();
        indexedFileSize = dis.readLong();
        indexedFileTS = dis.readLong();
        indexedFileMD5 = dis.readString();
        flags = dis.readInt();
        if (version < 3 && (flags & SEQUENCE_DICTIONARY_FLAG) == SEQUENCE_DICTIONARY_FLAG) {
            readSequenceDictionary(dis);
        }

        if (version >= 3) {
            int nProperties = dis.readInt();
            while (nProperties-- > 0) {
                String key = dis.readString();
                String value = dis.readString();
                properties.put(key, value);
            }
        }
    }


    /**
     * Kept to maintain backward compatibility with pre version 3 indexes.  The sequence dictionary is no longer
     * used,  use getSequenceNames() instead.
     *
     * @param dis
     * @throws IOException
     */
    private void readSequenceDictionary(LittleEndianInputStream dis) throws IOException {
        int size = dis.readInt();
        if (size < 0) throw new IllegalStateException("Size of the sequence dictionary entries is negitive");
        for (int x = 0; x < size; x++) {
            dis.readString();
            dis.readInt();
        }
    }

    public LinkedHashSet<String> getSequenceNames() {
        return new LinkedHashSet(chrIndeces.keySet());
    }

    /**
     * @param chr
     * @param start
     * @param end
     * @return
     */
    public List<Block> getBlocks(String chr, int start, int end) {

        ChrIndex chrIdx = chrIndeces.get(chr);
        if (chrIdx == null) {

            // Todo -- throw an exception ?
            return null;
        } else {
            return chrIdx.getBlocks(start, end);
        }
    }

    public abstract Class getIndexClass();

    /**
     * Store self to a file
     *
     * @param stream the input stream
     * @throws java.io.IOException
     */
    public void write(LittleEndianOutputStream stream) throws IOException {
        writeHeader(stream);

        //# of chromosomes
        stream.writeInt(chrIndeces.size());
        for (ChrIndex chrIdx : chrIndeces.values()) {
            chrIdx.write(stream);
        }
    }


    public void read(LittleEndianInputStream dis) throws IOException {

        try {
            readHeader(dis);

            int nChromosomes = dis.readInt();
            chrIndeces = new LinkedHashMap<String, ChrIndex>(nChromosomes * 2);

            while (nChromosomes-- > 0) {
                ChrIndex chrIdx = (ChrIndex) getIndexClass().newInstance();
                chrIdx.read(dis);
                chrIndeces.put(chrIdx.getName(), chrIdx);
            }

        } catch (InstantiationException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IllegalAccessException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            dis.close();
        }

    }
}
