/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed 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 com.android.camera.gallery;

import android.net.Uri;

import com.android.camera.ImageManager;
import com.android.camera.Util;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.PriorityQueue;

/**
 * A union of different <code>IImageList</code>. This class can merge several
 * <code>IImageList</code> into one list and sort them according to the
 * timestamp (The sorting must be same as all the given lists).
 */
public class ImageListUber implements IImageList {
    @SuppressWarnings("unused")
    private static final String TAG = "ImageListUber";

    private final IImageList [] mSubList;
    private final PriorityQueue<MergeSlot> mQueue;

    // This is an array of Longs wherein each Long consists of two components:
    // "a number" and "an index of sublist".
    //   * The lower 32bit indicates the number of consecutive entries that
    //     belong to a given sublist.
    //
    //   * The higher 32bit component indicates which sublist we're referring
    //     to.
    private long[] mSkipList;
    private int mSkipListSize;
    private int [] mSkipCounts;
    private int mLastListIndex;

    public ImageListUber(IImageList [] sublist, int sort) {
        mSubList = sublist.clone();
        mQueue = new PriorityQueue<MergeSlot>(4,
                sort == ImageManager.SORT_ASCENDING
                ? new AscendingComparator()
                : new DescendingComparator());
        mSkipList = new long[16];
        mSkipListSize = 0;
        mSkipCounts = new int[mSubList.length];
        mLastListIndex = -1;
        mQueue.clear();
        for (int i = 0, n = mSubList.length; i < n; ++i) {
            IImageList list = mSubList[i];
            MergeSlot slot = new MergeSlot(list, i);
            if (slot.next()) mQueue.add(slot);
        }
    }

    public HashMap<String, String> getBucketIds() {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        for (IImageList list : mSubList) {
            hashMap.putAll(list.getBucketIds());
        }
        return hashMap;
    }

    public int getCount() {
        int count = 0;
        for (IImageList subList : mSubList) {
            count += subList.getCount();
        }
        return count;
    }

    public boolean isEmpty() {
        for (IImageList subList : mSubList) {
            if (!subList.isEmpty()) return false;
        }
        return true;
    }

    // mSkipCounts is used to tally the counts as we traverse
    // the mSkipList.  It's a member variable only so that
    // we don't have to allocate each time through.  Otherwise
    // it could just as easily be a local.

    public IImage getImageAt(int index) {
        if (index < 0 || index > getCount()) {
            throw new IndexOutOfBoundsException(
                    "index " + index + " out of range max is " + getCount());
        }

        int skipCounts[] = mSkipCounts;
        // zero out the mSkipCounts since that's only used for the
        // duration of the function call.
        Arrays.fill(skipCounts, 0);

        // a counter of how many images we've skipped in
        // trying to get to index.  alternatively we could
        // have decremented index but, alas, I liked this
        // way more.
        int skipCount = 0;

        // scan the existing mSkipList to see if we've computed
        // enough to just return the answer
        for (int i = 0, n = mSkipListSize; i < n; ++i) {
            long v = mSkipList[i];

            int offset = (int) (v & 0xFFFFFFFF);
            int which  = (int) (v >> 32);
            if (skipCount + offset > index) {
                int subindex = mSkipCounts[which] + (index - skipCount);
                return mSubList[which].getImageAt(subindex);
            }
            skipCount += offset;
            mSkipCounts[which] += offset;
        }

        for (; true; ++skipCount) {
            MergeSlot slot = nextMergeSlot();
            if (slot == null) return null;
            if (skipCount == index) {
                IImage result = slot.mImage;
                if (slot.next()) mQueue.add(slot);
                return result;
            }
            if (slot.next()) mQueue.add(slot);
        }
    }

    private MergeSlot nextMergeSlot() {
        MergeSlot slot = mQueue.poll();
        if (slot == null) return null;
        if (slot.mListIndex == mLastListIndex) {
            int lastIndex = mSkipListSize - 1;
            ++mSkipList[lastIndex];
        } else {
            mLastListIndex = slot.mListIndex;
            if (mSkipList.length == mSkipListSize) {
                long [] temp = new long[mSkipListSize * 2];
                System.arraycopy(mSkipList, 0, temp, 0, mSkipListSize);
                mSkipList = temp;
            }
            mSkipList[mSkipListSize++] = (((long) mLastListIndex) << 32) | 1;
        }
        return slot;
    }

    public IImage getImageForUri(Uri uri) {
        for (IImageList sublist : mSubList) {
            IImage image = sublist.getImageForUri(uri);
            if (image != null) return image;
        }
        return null;
    }

    /**
     * Modify the skip list when an image is deleted by finding
     * the relevant entry in mSkipList and decrementing the
     * counter.  This is simple because deletion can never
     * cause change the order of images.
     */
    private void modifySkipCountForDeletedImage(int index) {
        int skipCount = 0;
        for (int i = 0, n = mSkipListSize; i < n; i++) {
            long v = mSkipList[i];
            int offset = (int) (v & 0xFFFFFFFF);
            if (skipCount + offset > index) {
                mSkipList[i] = v - 1;
                break;
            }
            skipCount += offset;
        }
    }

    private boolean removeImage(IImage image, int index) {
        IImageList list = image.getContainer();
        if (list != null && list.removeImage(image)) {
            modifySkipCountForDeletedImage(index);
            return true;
        }
        return false;
    }

    public boolean removeImage(IImage image) {
        return removeImage(image, getImageIndex(image));
    }

    public boolean removeImageAt(int index) {
        IImage image = getImageAt(index);
        if (image == null) return false;
        return removeImage(image, index);
    }

    public synchronized int getImageIndex(IImage image) {
        IImageList list = image.getContainer();
        int listIndex = Util.indexOf(mSubList, list);
        if (listIndex == -1) {
            throw new IllegalArgumentException();
        }
        int listOffset = list.getImageIndex(image);

        // Similar algorithm as getImageAt(int index)
        int skipCount = 0;
        for (int i = 0, n = mSkipListSize; i < n; ++i) {
            long value = mSkipList[i];
            int offset = (int) (value & 0xFFFFFFFF);
            int which  = (int) (value >> 32);
            if (which == listIndex) {
                if (listOffset < offset) {
                    return skipCount + listOffset;
                }
                listOffset -= offset;
            }
            skipCount += offset;
        }

        for (; true; ++skipCount) {
            MergeSlot slot = nextMergeSlot();
            if (slot == null) return -1;
            if (slot.mImage == image) {
                if (slot.next()) mQueue.add(slot);
                return skipCount;
            }
            if (slot.next()) mQueue.add(slot);
        }
    }

    private static class DescendingComparator implements Comparator<MergeSlot> {

        public int compare(MergeSlot m1, MergeSlot m2) {
            if (m1.mDateTaken != m2.mDateTaken) {
                return m1.mDateTaken < m2.mDateTaken ? 1 : -1;
            }
            return m1.mListIndex - m2.mListIndex;
        }
    }

    private static class AscendingComparator implements Comparator<MergeSlot> {

        public int compare(MergeSlot m1, MergeSlot m2) {
            if (m1.mDateTaken != m2.mDateTaken) {
                return m1.mDateTaken < m2.mDateTaken ? -1 : 1;
            }
            return m1.mListIndex - m2.mListIndex;
        }
    }

    /**
     * A merging slot is used to trace the current position of a sublist. For
     * each given sub list, there will be one corresponding merge slot. We
     * use merge-sort-like algorithm to build the merged list. At begining,
     * we put all the slots in a sorted heap (by timestamp). Each time, we
     * pop the slot with earliest timestamp out, get the image, and then move
     * the index forward, and put it back to the heap.
     */
    private static class MergeSlot {
        private int mOffset = -1;
        private final IImageList mList;

        int mListIndex;
        long mDateTaken;
        IImage mImage;

        public MergeSlot(IImageList list, int index) {
            mList = list;
            mListIndex = index;
        }

        public boolean next() {
            if (mOffset >= mList.getCount() - 1) return false;
            mImage = mList.getImageAt(++mOffset);
            mDateTaken = mImage.getDateTaken();
            return true;
        }
    }

    public void close() {
        for (int i = 0, n = mSubList.length; i < n; ++i) {
            mSubList[i].close();
        }
    }
}
