/*******************************************************************************
 *      Copyright (C) 2012 Google Inc.
 *      Licensed to 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.mail.browse;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;

import com.android.mail.utils.MatrixCursorWithCachedColumns;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

/**
 * TestProvider is a ContentProvider that can be used to simulate the storage and retrieval of
 * rows from any ContentProvider, even if that provider does not exist in the caller's package.
 *
 * It is specifically designed to enable testing of sync adapters that create
 * ContentProviderOperations (CPOs) that are then executed using ContentResolver.applyBatch().
 * Why is this useful? Because we can't instantiate CalendarProvider or ContactsProvider from our
 * package, as required by MockContentResolver.addProvider()
 *
 * Usage:
 *
 * ContentResolver.applyBatch(MockProvider.AUTHORITY, batch) will cause the CPOs to be executed,
 * returning an array of ContentProviderResult; in the case of inserts, the result will include
 * a Uri that can be used via query(). Note that the CPOs can contain references to any authority.
 *
 * query() does not allow non-null selection, selectionArgs, or sortOrder arguments; the
 * presence of these will result in an UnsupportedOperationException insert() acts as expected,
 * returning a Uri that can be directly used in a query
 *
 * delete() and update() do not allow non-null selection or selectionArgs arguments; the presence
 * of these will result in an UnsupportedOperationException
 *
 * NOTE: When using any operation other than applyBatch, the Uri to be used must be created with
 * MockProvider.uri(yourUri). This guarantees that the operation is sent to MockProvider
 *
 * NOTE: MockProvider only simulates direct storage/retrieval of rows; it does not (and can not)
 * simulate other actions (e.g. creation of ancillary data) that the actual provider might perform
 *
 * NOTE: See MockProviderTests for usage examples
 **/
public class TestProvider extends ContentProvider {
    public static final String AUTHORITY = "com.android.mail.mock.provider";
    /* package */static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    /* package */static final int TABLE = 100;
    /* package */static final int RECORD = 101;

    public static final String ID_COLUMN = "_id";

    public TestProvider() {
        super();
    }

    public TestProvider(Context context) {
        this();
        attachInfo(context, null);
    }

    // We'll store our values here
    private HashMap<String, ContentValues> mMockStore = new HashMap<String, ContentValues>();
    // And we'll generate new id's from here
    long mMockId = 1;

    /**
     * Create a Uri for MockProvider from a given Uri
     *
     * @param uri the Uri from which the MockProvider Uri will be created
     * @return a Uri that can be used with MockProvider
     */
    public static Uri uri(Uri uri) {
        return new Uri.Builder().scheme("content").authority(AUTHORITY)
                .path(uri.getPath().substring(1)).build();
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        if (selection != null || selectionArgs != null) {
            throw new UnsupportedOperationException();
        }
        String path = uri.getPath();
        if (mMockStore.containsKey(path)) {
            mMockStore.remove(path);
            return 1;
        } else {
            return 0;
        }
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // Remove the leading slash
        String table = uri.getPath().substring(1);
        long id = mMockId++;
        Uri newUri = new Uri.Builder().scheme("content").authority(AUTHORITY).path(table)
                .appendPath(Long.toString(id)).build();
        // Remember to store the _id
        values.put(ID_COLUMN, id);
        mMockStore.put(newUri.getPath(), values);
        int match = sURIMatcher.match(uri);
        if (match == UriMatcher.NO_MATCH) {
            sURIMatcher.addURI(AUTHORITY, table, TABLE);
            sURIMatcher.addURI(AUTHORITY, table + "/#", RECORD);
        }
        return newUri;
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        if (selection != null || selectionArgs != null || sortOrder != null || projection == null) {
            throw new UnsupportedOperationException();
        }
        final int match = sURIMatcher.match(uri(uri));
        ArrayList<ContentValues> valuesList = new ArrayList<ContentValues>();
        switch (match) {
            case TABLE:
                Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet();
                String prefix = uri.getPath() + "/";
                for (Entry<String, ContentValues> entry : entrySet) {
                    if (entry.getKey().startsWith(prefix)) {
                        valuesList.add(entry.getValue());
                    }
                }
                break;
            case RECORD:
                ContentValues values = mMockStore.get(uri.getPath());
                if (values != null) {
                    valuesList.add(values);
                }
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        MatrixCursor cursor = new MatrixCursorWithCachedColumns(projection, 1);
        for (ContentValues cv : valuesList) {
            Object[] rowValues = new Object[projection.length];
            int i = 0;
            for (String column : projection) {
                rowValues[i++] = cv.get(column);
            }
            cursor.addRow(rowValues);
        }
        return cursor;
    }

    @Override
    public int update(Uri uri, ContentValues newValues, String selection, String[] selectionArgs) {
        if (selection != null || selectionArgs != null) {
            throw new UnsupportedOperationException();
        }
        final int match = sURIMatcher.match(uri(uri));
        ArrayList<ContentValues> updateValuesList = new ArrayList<ContentValues>();
        String path = uri.getPath();
        switch (match) {
            case TABLE:
                Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet();
                String prefix = path + "/";
                for (Entry<String, ContentValues> entry : entrySet) {
                    if (entry.getKey().startsWith(prefix)) {
                        updateValuesList.add(entry.getValue());
                    }
                }
                break;
            case RECORD:
                ContentValues cv = mMockStore.get(path);
                if (cv != null) {
                    updateValuesList.add(cv);
                }
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        Set<Entry<String, Object>> newValuesSet = newValues.valueSet();
        for (Entry<String, Object> entry : newValuesSet) {
            String key = entry.getKey();
            Object value = entry.getValue();
            for (ContentValues targetValues : updateValuesList) {
                if (value instanceof Integer) {
                    targetValues.put(key, (Integer) value);
                } else if (value instanceof Long) {
                    targetValues.put(key, (Long) value);
                } else if (value instanceof String) {
                    targetValues.put(key, (String) value);
                } else if (value instanceof Boolean) {
                    targetValues.put(key, (Boolean) value);
                } else {
                    throw new IllegalArgumentException();
                }
            }
        }
        for (ContentValues targetValues : updateValuesList) {
            switch(match) {
                case TABLE:
                    mMockStore.put(path + "/" + targetValues.getAsLong(ID_COLUMN), targetValues);
                    break;
                case RECORD:
                    mMockStore.put(path, targetValues);
                    break;
            }
        }
        return updateValuesList.size();
    }
}
