// Stack.java
// (C) 2009 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 03.07.2009 on http://yacy.net
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package net.yacy.kelondro.blob;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import net.yacy.cora.order.NaturalOrder;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.cora.util.SpaceExceededException;


public class Stack {

    private final Heap stack;
    private long lastHandle;

    /**
     * create a new stack object.
     * a stack object is backed by a blob file that contains the stack entries.
     * all stack entries can be accessed with a long handle; the handle is
     * represented as b256-encoded byte[] as key in the blob.
     * The handle is created using the current time. That means that the top
     * element on the stack has the maximum time as key handle and the element
     * at the bottom of the stack has the minimum time as key handle
     * @param stackFile
     * @throws IOException
     */
    public Stack(final File stackFile) throws IOException {
        this.stack = new Heap(stackFile, 8, NaturalOrder.naturalOrder, 0);
        this.lastHandle = 0;
    }

    /**
     * clear the stack content
     * @throws IOException
     */
    public void clear() throws IOException {
        this.stack.clear();
    }

    /**
     * create a new time handle. In case that the method is called
     * within a single millisecond twice, a new handle is created using
     * an increment of the previous handle to avoid handle collisions.
     * This method must be called in an synchronized environment
     * @return a unique handle for this stack
     */
    private long nextHandle() {
        long h = System.currentTimeMillis();
        if (h <= this.lastHandle) h = this.lastHandle + 1;
        this.lastHandle = h;
        return h;
    }

    /**
     * Iterate all handles from the stack as Long numbers
     * @return an iterator of all handles of the stack
     * @throws IOException
     */
    public synchronized Iterator<Long> handles() throws IOException {
        return NaturalOrder.LongIterator(this.stack.keys(true, false));
    }

    /**
     * get the size of a stack
     * @return the number of entries on the stack
     */
    public synchronized int size() {
        return this.stack.size();
    }

    /**
     * push a new element on the top of the stack
     * @param b the new stack element
     * @return the handle used to store the new element
     * @throws IOException
     * @throws SpaceExceededException
     */
    public synchronized long push(final byte[] b) throws IOException, SpaceExceededException {
        long handle = nextHandle();
        this.stack.insert(NaturalOrder.encodeLong(handle, 8), b);
        return handle;
    }

    /**
     * push a new element on the top of the stack using a entry object
     * this is only useful for internal processes where a special handle
     * is created
     * @param e the new stack element
     * @return the handle used to store the new element
     * @throws IOException
     * @throws SpaceExceededException
     */
    protected synchronized void push(final Entry e) throws IOException, SpaceExceededException {
        this.stack.insert(NaturalOrder.encodeLong(e.h, 8), e.b);
    }

    /**
     * get an element from the stack using the handle
     * @param handle
     * @return the object that belongs to the handle
     *         or null if no such element exists
     * @throws IOException
     * @throws SpaceExceededException
     */
    public synchronized byte[] get(final long handle) throws IOException, SpaceExceededException {
        byte[] k = NaturalOrder.encodeLong(handle, 8);
        byte[] b = this.stack.get(k);
        if (b == null) return null;
        return b;
    }

    /**
     * remove an element from the stack using the entry handle
     * @param handle
     * @return the removed element
     * @throws IOException
     * @throws SpaceExceededException
     */
    public synchronized byte[] remove(final long handle) throws IOException, SpaceExceededException {
        byte[] k = NaturalOrder.encodeLong(handle, 8);
        byte[] b = this.stack.get(k);
        if (b == null) return null;
        this.stack.delete(k);
        return b;
    }

    /**
     * remove the top element from the stack
     * @return the top element or null if the stack is empty
     * @throws IOException
     */
    public synchronized Entry pop() throws IOException {
        return po(this.stack.lastKey(), true);
    }

    /**
     * return the top element of the stack.
     * The element is not removed from the stack.
     * Successive calls to this method will always return the same element
     * @return the element on the top of the stack or null, if stack is empty
     * @throws IOException
     */
    public synchronized Entry top() throws IOException {
        return po(this.stack.lastKey(), false);
    }

    /**
     * remove the bottom element from the stack
     * @return the bottom element or null if the stack is empty
     * @throws IOException
     */
    public synchronized Entry pot() throws IOException {
        return po(this.stack.firstKey(), true);
    }

    /**
     * return the bottom element of the stack.
     * The element is not removed from the stack.
     * Successive calls to this method will always return the same element
     * @return the element on the bottom of the stack or null, if stack is empty
     * @throws IOException
     */
    public synchronized Entry bot() throws IOException {
        return po(this.stack.firstKey(), false);
    }

    private Entry po(final byte[] k, final boolean remove) throws IOException {
        if (k == null) return null;
        assert k.length == 8;
        byte[] b;
        try {
            b = this.stack.get(k);
        } catch (final SpaceExceededException e) {
            ConcurrentLog.logException(e);
            b = null;
        }
        assert b != null;
        if (b == null) return null;
        if (remove) this.stack.delete(k);
        return new Entry(k, b);
    }

    public class Entry {

        private long h;
        byte[] b;

        /**
         * create a new entry object using a long handle
         * @param h
         * @param b
         */
        public Entry(final long h, final byte[] b) {
            this.h = h;
            this.b = b;
        }

        /**
         * create a new entry object using the byte[] encoded handle
         * @param k
         * @param b
         */
        public Entry(final byte[] k, final byte[] b) {
            this.h = NaturalOrder.decodeLong(k);
            this.b = b;
        }

        /**
         * get the handle
         * @return the handle
         */
        public long handle() {
            return this.h;
        }

        /**
         * get the blob entry
         * @return the blob
         */
        public byte[] blob() {
            return this.b;
        }
    }

    /**
     * close the stack file and write a handle index
     */
    public synchronized void close() {
        this.stack.close(true);
    }
}
