/*
* Copyright (C) 2015 Samsung System LSI
* 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.bluetooth.map;

import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.Telephony.CanonicalAddressesColumns;
import android.provider.Telephony.MmsSms;
import android.util.Log;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Arrays;
import java.util.HashMap;
import java.util.regex.Pattern;

/**
 * Use these functions when extracting data for listings. It caches frequently used data to
 * speed up building large listings - e.g. before applying filtering.
 */
@TargetApi(19)
public class SmsMmsContacts {

    private static final String TAG = "SmsMmsContacts";

    private HashMap<Long, String> mPhoneNumbers = null;
    @VisibleForTesting
    final HashMap<String, MapContact> mNames = new HashMap<String, MapContact>(10);

    private static final Uri ADDRESS_URI =
            MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();

    @VisibleForTesting
    static final String[] ADDRESS_PROJECTION = {
            CanonicalAddressesColumns._ID, CanonicalAddressesColumns.ADDRESS
    };
    private static final int COL_ADDR_ID =
            Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns._ID);
    private static final int COL_ADDR_ADDR =
            Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS);

    @VisibleForTesting
    static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
    private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
    private static final int COL_CONTACT_ID =
            Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID);
    private static final int COL_CONTACT_NAME =
            Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts.DISPLAY_NAME);

    /**
     * Get a contacts phone number based on the canonical addresses id of the contact.
     * (The ID listed in the Threads table.)
     * @param resolver the ContantResolver to be used.
     * @param id the id of the contact, as listed in the Threads table
     * @return the phone number of the contact - or null if id does not exist.
     */
    public String getPhoneNumber(ContentResolver resolver, long id) {
        String number;
        if (mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) {
            return number;
        }
        fillPhoneCache(resolver);
        return mPhoneNumbers.get(id);
    }

    public static String getPhoneNumberUncached(ContentResolver resolver, long id) {
        String where = CanonicalAddressesColumns._ID + " = " + id;
        Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(resolver, ADDRESS_URI,
                ADDRESS_PROJECTION, where, null, null);
        try {
            if (c != null) {
                if (c.moveToPosition(0)) {
                    return c.getString(COL_ADDR_ADDR);
                }
            }
            Log.e(TAG, "query failed");
        } finally {
            if (c != null) {
                c.close();
            }
        }
        return null;
    }

    /**
     * Clears the local cache. Call after a listing is complete, to avoid using invalid data.
     */
    public void clearCache() {
        if (mPhoneNumbers != null) {
            mPhoneNumbers.clear();
        }
        if (mNames != null) {
            mNames.clear();
        }
    }

    /**
     * Refreshes the cache, by clearing all cached values and fill the cache with the result of
     * a new query.
     * @param resolver the ContantResolver to be used.
     */
    @VisibleForTesting
    void fillPhoneCache(ContentResolver resolver) {
        Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(resolver, ADDRESS_URI,
                ADDRESS_PROJECTION, null, null, null);
        if (mPhoneNumbers == null) {
            int size = 0;
            if (c != null) {
                size = c.getCount();
            }
            mPhoneNumbers = new HashMap<Long, String>(size);
        } else {
            mPhoneNumbers.clear();
        }
        try {
            if (c != null) {
                long id;
                String addr;
                c.moveToPosition(-1);
                while (c.moveToNext()) {
                    id = c.getLong(COL_ADDR_ID);
                    addr = c.getString(COL_ADDR_ADDR);
                    mPhoneNumbers.put(id, addr);
                }
            } else {
                Log.e(TAG, "query failed");
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    public MapContact getContactNameFromPhone(String phone, ContentResolver resolver) {
        return getContactNameFromPhone(phone, resolver, null);
    }

    /**
     * Lookup a contacts name in the Android Contacts database.
     * @param phone the phone number of the contact
     * @param resolver the ContentResolver to use.
     * @return the name of the contact or null, if no contact was found.
     */
    public MapContact getContactNameFromPhone(String phone, ContentResolver resolver,
            String contactNameFilter) {
        MapContact contact = mNames.get(phone);

        if (contact != null) {
            if (contact.getId() < 0) {
                return null;
            }
            if (contactNameFilter == null) {
                return contact;
            }
            // Validate filter
            String searchString = contactNameFilter.replace("*", ".*");
            searchString = ".*" + searchString + ".*";
            Pattern p = Pattern.compile(Pattern.quote(searchString), Pattern.CASE_INSENSITIVE);
            if (p.matcher(contact.getName()).find()) {
                return contact;
            }
            return null;
        }

        // TODO: Should we change to extract both formatted name, and display name?

        Uri uri =
                Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone));
        String selection = CONTACT_SEL_VISIBLE;
        String[] selectionArgs = null;
        if (contactNameFilter != null) {
            selection += "AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?";
            selectionArgs = new String[]{"%" + contactNameFilter.replace("*", "%") + "%"};
        }

        Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(resolver, uri,
                CONTACT_PROJECTION, selection, selectionArgs, null);
        try {
            if (c != null && c.getCount() >= 1) {
                c.moveToFirst();
                long id = c.getLong(COL_CONTACT_ID);
                String name = c.getString(COL_CONTACT_NAME);
                contact = MapContact.create(id, name);
                mNames.put(phone, contact);
            } else {
                contact = MapContact.create(-1, null);
                mNames.put(phone, contact);
                contact = null;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
        return contact;
    }
}
