/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.activemq.kaha.impl.container;

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

import org.apache.activemq.kaha.ContainerId;
import org.apache.activemq.kaha.RuntimeStoreException;
import org.apache.activemq.kaha.StoreEntry;
import org.apache.activemq.kaha.impl.DataManager;
import org.apache.activemq.kaha.impl.data.Item;
import org.apache.activemq.kaha.impl.index.DiskIndexLinkedList;
import org.apache.activemq.kaha.impl.index.IndexItem;
import org.apache.activemq.kaha.impl.index.IndexLinkedList;
import org.apache.activemq.kaha.impl.index.IndexManager;
import org.apache.activemq.kaha.impl.index.VMIndexLinkedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of a ListContainer
 * 
 * 
 */
public abstract class BaseContainerImpl {

    private static final Logger LOG = LoggerFactory.getLogger(BaseContainerImpl.class);
    protected IndexItem root;
    protected IndexLinkedList indexList;
    protected IndexManager indexManager;
    protected DataManager dataManager;
    protected ContainerId containerId;
    protected boolean loaded;
    protected boolean closed;
    protected boolean initialized;
    protected boolean persistentIndex;

    protected BaseContainerImpl(ContainerId id, IndexItem root, IndexManager indexManager, DataManager dataManager, boolean persistentIndex) {
        this.containerId = id;
        this.root = root;
        this.indexManager = indexManager;
        this.dataManager = dataManager;
        this.persistentIndex = persistentIndex;
    }

    public ContainerId getContainerId() {
        return containerId;
    }

    public synchronized void init() {
        if (!initialized) {
            if (!initialized) {
                initialized = true;
                if (this.indexList == null) {
                    if (persistentIndex) {
                        this.indexList = new DiskIndexLinkedList(indexManager, root);
                    } else {
                        this.indexList = new VMIndexLinkedList(root);
                    }
                }
            }
        }
    }

    public synchronized void clear() {
        if (indexList != null) {
            indexList.clear();
        }
    }

    /**
     * @return the indexList
     */
    public IndexLinkedList getList() {
        return indexList;
    }

    /**
     * @param indexList the indexList to set
     */
    public void setList(IndexLinkedList indexList) {
        this.indexList = indexList;
    }

    public abstract void unload();

    public abstract void load();

    public abstract int size();

    protected abstract Object getValue(StoreEntry currentItem);

    protected abstract void remove(IndexItem currentItem);

    protected final synchronized IndexLinkedList getInternalList() {
        return indexList;
    }

    public final synchronized void close() {
        unload();
        closed = true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.activemq.kaha.ListContainer#isLoaded()
     */
    public final synchronized boolean isLoaded() {
        checkClosed();
        return loaded;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.activemq.kaha.ListContainer#getId()
     */
    public final Object getId() {
        checkClosed();
        return containerId.getKey();
    }

    public DataManager getDataManager() {
        return dataManager;
    }

    public IndexManager getIndexManager() {
        return indexManager;
    }

    public final synchronized void expressDataInterest() throws IOException {
        long nextItem = root.getNextItem();
        while (nextItem != Item.POSITION_NOT_SET) {
            IndexItem item = indexManager.getIndex(nextItem);
            item.setOffset(nextItem);
            dataManager.addInterestInFile(item.getKeyFile());
            dataManager.addInterestInFile(item.getValueFile());
            nextItem = item.getNextItem();
        }
    }

    protected final void doClear() {
        checkClosed();
        loaded = true;
        List<IndexItem> indexList = new ArrayList<IndexItem>();
        try {
            init();
            long nextItem = root.getNextItem();
            while (nextItem != Item.POSITION_NOT_SET) {
                IndexItem item = new IndexItem();
                item.setOffset(nextItem);
                indexList.add(item);
                nextItem = item.getNextItem();
            }
            root.setNextItem(Item.POSITION_NOT_SET);
            storeIndex(root);
            for (int i = 0; i < indexList.size(); i++) {
                IndexItem item = indexList.get(i);
                dataManager.removeInterestInFile(item.getKeyFile());
                dataManager.removeInterestInFile(item.getValueFile());
                indexManager.freeIndex(item);
            }
            indexList.clear();
        } catch (IOException e) {
            LOG.error("Failed to clear Container " + getId(), e);
            throw new RuntimeStoreException(e);
        }
    }

    protected final void delete(final IndexItem keyItem, final IndexItem prevItem, final IndexItem nextItem) {
        if (keyItem != null) {
            try {
                root = indexList.getRoot();
                IndexItem prev = prevItem == null ? root : prevItem;
                IndexItem next = (nextItem == null || !nextItem.equals(root)) ? nextItem : null;
                dataManager.removeInterestInFile(keyItem.getKeyFile());
                dataManager.removeInterestInFile(keyItem.getValueFile());
                if (next != null) {
                    prev.setNextItem(next.getOffset());
                    next.setPreviousItem(prev.getOffset());
                    updateIndexes(next);
                } else {
                    prev.setNextItem(Item.POSITION_NOT_SET);
                }
                updateIndexes(prev);
                indexManager.freeIndex(keyItem);
            } catch (IOException e) {
                LOG.error("Failed to delete " + keyItem, e);
                throw new RuntimeStoreException(e);
            }
        }
    }

    protected final void checkClosed() {
        if (closed) {
            throw new RuntimeStoreException("The store is closed");
        }
    }

    protected void storeIndex(IndexItem item) throws IOException {
        indexManager.storeIndex(item);
    }

    protected void updateIndexes(IndexItem item) throws IOException {
        indexManager.updateIndexes(item);
    }

    protected final boolean isRoot(StoreEntry item) {
        return item != null && root != null && (root == item || root.getOffset() == item.getOffset());
        // return item != null && indexRoot != null && indexRoot == item;
    }

}
