/*
 * Copyright (c) 2019 Hemanth Savarala.
 *
 * Licensed under the GNU General Public License v3
 *
 * This 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 3 of the License, or (at your option) any later version.
 *
 * This software 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.
 */
package code.name.monkey.retromusic.loaders;

import android.database.AbstractCursor;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

/**
 * This cursor basically wraps a song cursor and is given a list of the order of the ids of the
 * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted
 * by moving the point to the appropriate spot
 */
public class SortedCursor extends AbstractCursor {
    // cursor to wrap
    private final Cursor mCursor;
    // the map of external indices to internal indices
    private ArrayList<Integer> mOrderedPositions;
    // this contains the ids that weren't found in the underlying cursor
    private ArrayList<String> mMissingValues;
    // this contains the mapped cursor positions and afterwards the extra ids that weren't found
    private HashMap<String, Integer> mMapCursorPositions;

    /**
     * @param cursor     to wrap
     * @param order      the list of unique ids in sorted order to display
     * @param columnName the column name of the id to look up in the internal cursor
     */
    public SortedCursor(@NonNull final Cursor cursor, @Nullable final String[] order, final String columnName) {
        mCursor = cursor;
        mMissingValues = buildCursorPositionMapping(order, columnName);
    }

    /**
     * This function populates mOrderedPositions with the cursor positions in the order based
     * on the order passed in
     *
     * @param order the target order of the internal cursor
     * @return returns the ids that aren't found in the underlying cursor
     */
    @NonNull
    private ArrayList<String> buildCursorPositionMapping(@Nullable final String[] order, final String columnName) {
        ArrayList<String> missingValues = new ArrayList<>();

        mOrderedPositions = new ArrayList<>(mCursor.getCount());

        mMapCursorPositions = new HashMap<>(mCursor.getCount());
        final int valueColumnIndex = mCursor.getColumnIndex(columnName);

        if (mCursor.moveToFirst()) {
            // first figure out where each of the ids are in the cursor
            do {
                mMapCursorPositions.put(mCursor.getString(valueColumnIndex), mCursor.getPosition());
            } while (mCursor.moveToNext());

            if (order != null) {
                // now create the ordered positions to map to the internal cursor given the
                // external sort order
                for (final String value : order) {
                    if (mMapCursorPositions.containsKey(value)) {
                        mOrderedPositions.add(mMapCursorPositions.get(value));
                        mMapCursorPositions.remove(value);
                    } else {
                        missingValues.add(value);
                    }
                }
            }

            mCursor.moveToFirst();
        }

        return missingValues;
    }

    /**
     * @return the list of ids that weren't found in the underlying cursor
     */
    public ArrayList<String> getMissingValues() {
        return mMissingValues;
    }

    /**
     * @return the list of ids that were in the underlying cursor but not part of the ordered list
     */
    @NonNull
    public Collection<String> getExtraValues() {
        return mMapCursorPositions.keySet();
    }

    @Override
    public void close() {
        mCursor.close();

        super.close();
    }

    @Override
    public int getCount() {
        return mOrderedPositions.size();
    }

    @Override
    public String[] getColumnNames() {
        return mCursor.getColumnNames();
    }

    @Override
    public String getString(int column) {
        return mCursor.getString(column);
    }

    @Override
    public short getShort(int column) {
        return mCursor.getShort(column);
    }

    @Override
    public int getInt(int column) {
        return mCursor.getInt(column);
    }

    @Override
    public long getLong(int column) {
        return mCursor.getLong(column);
    }

    @Override
    public float getFloat(int column) {
        return mCursor.getFloat(column);
    }

    @Override
    public double getDouble(int column) {
        return mCursor.getDouble(column);
    }

    @Override
    public boolean isNull(int column) {
        return mCursor.isNull(column);
    }

    @Override
    public boolean onMove(int oldPosition, int newPosition) {
        if (newPosition >= 0 && newPosition < getCount()) {
            mCursor.moveToPosition(mOrderedPositions.get(newPosition));
            return true;
        }

        return false;
    }
}
