/**
 * Copyright (c) 2012 Todoroo Inc
 *
 * See the file "LICENSE" for the full license governing this code.
 */
package com.todoroo.andlib.data;

import java.util.WeakHashMap;

import android.database.Cursor;
import android.database.CursorWrapper;

import com.todoroo.andlib.data.Property.PropertyVisitor;

/**
 * AstridCursor wraps a cursor and allows users to query for individual
 * {@link Property} types or read an entire {@link AbstractModel} from
 * a database row.
 *
 * @author Tim Su <tim@todoroo.com>
 *
 * @param <TYPE> a model type that is returned by this cursor
 */
public class TodorooCursor<TYPE extends AbstractModel> extends CursorWrapper {

    /** Properties read by this cursor */
    private final Property<?>[] properties;

    /** Weakly cache field name to column id references for this cursor.
     * Because it's a weak hash map, entire keys can be discarded by GC */
    private final WeakHashMap<String, Integer> columnIndexCache;

    /** Property reading visitor */
    private static final CursorReadingVisitor reader = new CursorReadingVisitor();

    /** Wrapped cursor */
    private final Cursor cursor;

    /**
     * Create an <code>AstridCursor</code> from the supplied {@link Cursor}
     * object.
     *
     * @param cursor
     * @param properties properties read from this cursor
     */
    public TodorooCursor(Cursor cursor, Property<?>[] properties) {
        super(cursor);

        this.cursor = cursor;
        this.properties = properties;
        columnIndexCache = new WeakHashMap<String, Integer>();
    }

    /**
     * Get the value for the given property on the underlying {@link Cursor}
     *
     * @param <PROPERTY_TYPE> type to return
     * @param property to retrieve
     * @return
     */
    public <PROPERTY_TYPE> PROPERTY_TYPE get(Property<PROPERTY_TYPE> property) {
        return (PROPERTY_TYPE)property.accept(reader, this);
    }

    /**
     * @return underlying cursor
     */
    public Cursor getCursor() {
        return cursor;
    }

    /**
     * Gets entire property list
     * @return
     */
    public Property<?>[] getProperties() {
        return properties;
    }

    /**
     * Use cache to get the column index for the given field name
     */
    public synchronized int getColumnIndexFromCache(String field) {
        Integer index = columnIndexCache.get(field);
        if(index == null) {
            index = getColumnIndexOrThrow(field);
            columnIndexCache.put(field, index);
        }

        return index;
    }

    /**
     * Visitor that reads the given property from a cursor
     *
     * @author Tim Su <tim@todoroo.com>
     *
     */
    public static class CursorReadingVisitor implements PropertyVisitor<Object, TodorooCursor<?>> {

        public Object visitDouble(Property<Double> property,
                TodorooCursor<?> cursor) {
            int column = columnIndex(property, cursor);
            if(property.nullable && cursor.isNull(column))
                return null;
            return cursor.getDouble(column);
        }

        public Object visitInteger(Property<Integer> property,
                TodorooCursor<?> cursor) {
            int column = columnIndex(property, cursor);
            if(property.nullable && cursor.isNull(column))
                return null;
            return cursor.getInt(column);
        }

        public Object visitLong(Property<Long> property, TodorooCursor<?> cursor) {
            int column = columnIndex(property, cursor);
            if(property.nullable && cursor.isNull(column))
                return null;
            return cursor.getLong(column);
        }

        public Object visitString(Property<String> property,
                TodorooCursor<?> cursor) {
            int column = columnIndex(property, cursor);
            if(property.nullable && cursor.isNull(column))
                return null;
            return cursor.getString(column);
        }

        private int columnIndex(Property<?> property, TodorooCursor<?> cursor) {
            return cursor.getColumnIndexFromCache(property.getColumnName());
        }
    }

}
