/*
 * Copyright (C) 2020 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.server.people.data;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.util.Slog;

/** A helper class that queries the Contacts database. */
class ContactsQueryHelper {

    private static final String TAG = "ContactsQueryHelper";

    private final Context mContext;
    private Uri mContactUri;
    private boolean mIsStarred;
    private String mPhoneNumber;
    private long mLastUpdatedTimestamp;

    ContactsQueryHelper(Context context) {
        mContext = context;
    }

    /**
     * Queries the Contacts database with the given contact URI and returns whether the query runs
     * successfully.
     */
    @WorkerThread
    boolean query(@NonNull String contactUri) {
        if (TextUtils.isEmpty(contactUri)) {
            return false;
        }
        Uri uri = Uri.parse(contactUri);
        if ("tel".equals(uri.getScheme())) {
            return queryWithPhoneNumber(uri.getSchemeSpecificPart());
        } else if ("mailto".equals(uri.getScheme())) {
            return queryWithEmail(uri.getSchemeSpecificPart());
        } else if (contactUri.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
            return queryWithUri(uri);
        }
        return false;
    }

    /** Queries the Contacts database and read the most recently updated contact. */
    @WorkerThread
    boolean querySince(long sinceTime) {
        final String[] projection = new String[] {
                Contacts._ID, Contacts.LOOKUP_KEY, Contacts.STARRED, Contacts.HAS_PHONE_NUMBER,
                Contacts.CONTACT_LAST_UPDATED_TIMESTAMP };
        String selection = Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?";
        String[] selectionArgs = new String[] {Long.toString(sinceTime)};
        return queryContact(Contacts.CONTENT_URI, projection, selection, selectionArgs);
    }

    @Nullable
    Uri getContactUri() {
        return mContactUri;
    }

    boolean isStarred() {
        return mIsStarred;
    }

    @Nullable
    String getPhoneNumber() {
        return mPhoneNumber;
    }

    long getLastUpdatedTimestamp() {
        return mLastUpdatedTimestamp;
    }

    private boolean queryWithPhoneNumber(String phoneNumber) {
        Uri phoneUri = Uri.withAppendedPath(
                ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
        return queryWithUri(phoneUri);
    }

    private boolean queryWithEmail(String email) {
        Uri emailUri = Uri.withAppendedPath(
                ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI, Uri.encode(email));
        return queryWithUri(emailUri);
    }

    private boolean queryWithUri(@NonNull Uri uri) {
        final String[] projection = new String[] {
                Contacts._ID, Contacts.LOOKUP_KEY, Contacts.STARRED, Contacts.HAS_PHONE_NUMBER };
        return queryContact(uri, projection, /* selection= */ null, /* selectionArgs= */ null);
    }

    private boolean queryContact(@NonNull Uri uri, @NonNull String[] projection,
            @Nullable String selection, @Nullable String[] selectionArgs) {
        long contactId;
        String lookupKey = null;
        boolean hasPhoneNumber = false;
        boolean found = false;
        try (Cursor cursor = mContext.getContentResolver().query(
                uri, projection, selection, selectionArgs, /* sortOrder= */ null)) {
            if (cursor == null) {
                Slog.w(TAG, "Cursor is null when querying contact.");
                return false;
            }
            while (cursor.moveToNext()) {
                // Contact ID
                int idIndex = cursor.getColumnIndex(Contacts._ID);
                contactId = cursor.getLong(idIndex);

                // Lookup key
                int lookupKeyIndex = cursor.getColumnIndex(Contacts.LOOKUP_KEY);
                lookupKey = cursor.getString(lookupKeyIndex);

                mContactUri = Contacts.getLookupUri(contactId, lookupKey);

                // Starred
                int starredIndex = cursor.getColumnIndex(Contacts.STARRED);
                mIsStarred = cursor.getInt(starredIndex) != 0;

                // Has phone number
                int hasPhoneNumIndex = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER);
                hasPhoneNumber = cursor.getInt(hasPhoneNumIndex) != 0;

                // Last updated timestamp
                int lastUpdatedTimestampIndex = cursor.getColumnIndex(
                        Contacts.CONTACT_LAST_UPDATED_TIMESTAMP);
                if (lastUpdatedTimestampIndex >= 0) {
                    mLastUpdatedTimestamp = cursor.getLong(lastUpdatedTimestampIndex);
                }

                found = true;
            }
        } catch (SQLiteException exception) {
            Slog.w("SQLite exception when querying contacts.", exception);
        } catch (IllegalArgumentException exception) {
            Slog.w("Illegal Argument exception when querying contacts.", exception);
        }
        if (found && lookupKey != null && hasPhoneNumber) {
            return queryPhoneNumber(lookupKey);
        }
        return found;
    }

    private boolean queryPhoneNumber(String lookupKey) {
        String[] projection = new String[] {
                ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER };
        String selection = Contacts.LOOKUP_KEY + " = ?";
        String[] selectionArgs = new String[] { lookupKey };
        try (Cursor cursor = mContext.getContentResolver().query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, selection,
                selectionArgs, /* sortOrder= */ null)) {
            if (cursor == null) {
                Slog.w(TAG, "Cursor is null when querying contact phone number.");
                return false;
            }
            while (cursor.moveToNext()) {
                // Phone number
                int phoneNumIdx = cursor.getColumnIndex(
                        ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
                if (phoneNumIdx >= 0) {
                    mPhoneNumber = cursor.getString(phoneNumIdx);
                }
            }
        }
        return true;
    }
}
