/*
 * Copyright (C) 2012 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.contacts.common;

import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.provider.ContactsContract;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.view.View;
import com.android.contacts.common.model.account.AccountType;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;

/** Shared static contact utility methods. */
public class MoreContactUtils {

  private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);

  /**
   * Returns true if two data with mimetypes which represent values in contact entries are
   * considered equal for collapsing in the GUI. For caller-id, use {@link
   * android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)} instead
   */
  public static boolean shouldCollapse(
      CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2) {
    // different mimetypes? don't collapse
    if (!TextUtils.equals(mimetype1, mimetype2)) {
      return false;
    }

    // exact same string? good, bail out early
    if (TextUtils.equals(data1, data2)) {
      return true;
    }

    // so if either is null, these two must be different
    if (data1 == null || data2 == null) {
      return false;
    }

    // if this is not about phone numbers, we know this is not a match (of course, some
    // mimetypes could have more sophisticated matching is the future, e.g. addresses)
    if (!TextUtils.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, mimetype1)) {
      return false;
    }

    return shouldCollapsePhoneNumbers(data1.toString(), data2.toString());
  }

  // TODO: Move this to PhoneDataItem.shouldCollapse override
  private static boolean shouldCollapsePhoneNumbers(String number1, String number2) {
    // Work around to address a bug. We want to distinguish between #555, *555 and 555.
    // This makes no attempt to distinguish between 555 and 55*5, since 55*5 is an improbable
    // number. PhoneNumberUtil already distinguishes between 555 and 55#5.
    if (number1.contains("#") != number2.contains("#")
        || number1.contains("*") != number2.contains("*")) {
      return false;
    }

    // Now do the full phone number thing. split into parts, separated by waiting symbol
    // and compare them individually
    final String[] dataParts1 = number1.split(WAIT_SYMBOL_AS_STRING);
    final String[] dataParts2 = number2.split(WAIT_SYMBOL_AS_STRING);
    if (dataParts1.length != dataParts2.length) {
      return false;
    }
    final PhoneNumberUtil util = PhoneNumberUtil.getInstance();
    for (int i = 0; i < dataParts1.length; i++) {
      // Match phone numbers represented by keypad letters, in which case prefer the
      // phone number with letters.
      final String dataPart1 = PhoneNumberUtils.convertKeypadLettersToDigits(dataParts1[i]);
      final String dataPart2 = dataParts2[i];

      // substrings equal? shortcut, don't parse
      if (TextUtils.equals(dataPart1, dataPart2)) {
        continue;
      }

      // do a full parse of the numbers
      final PhoneNumberUtil.MatchType result = util.isNumberMatch(dataPart1, dataPart2);
      switch (result) {
        case NOT_A_NUMBER:
          // don't understand the numbers? let's play it safe
          return false;
        case NO_MATCH:
          return false;
        case EXACT_MATCH:
          break;
        case NSN_MATCH:
          try {
            // For NANP phone numbers, match when one has +1 and the other does not.
            // In this case, prefer the +1 version.
            if (util.parse(dataPart1, null).getCountryCode() == 1) {
              // At this point, the numbers can be either case 1 or 2 below....
              //
              // case 1)
              // +14155551212    <--- country code 1
              //  14155551212    <--- 1 is trunk prefix, not country code
              //
              // and
              //
              // case 2)
              // +14155551212
              //   4155551212
              //
              // From a bug, case 2 needs to be equal.  But also that bug, case 3
              // below should not be equal.
              //
              // case 3)
              // 14155551212
              //  4155551212
              //
              // So in order to make sure transitive equality is valid, case 1 cannot
              // be equal.  Otherwise, transitive equality breaks and the following
              // would all be collapsed:
              //   4155551212  |
              //  14155551212  |---->   +14155551212
              // +14155551212  |
              //
              // With transitive equality, the collapsed values should be:
              //   4155551212  |         14155551212
              //  14155551212  |---->   +14155551212
              // +14155551212  |

              // Distinguish between case 1 and 2 by checking for trunk prefix '1'
              // at the start of number 2.
              if (dataPart2.trim().charAt(0) == '1') {
                // case 1
                return false;
              }
              break;
            }
          } catch (NumberParseException e) {
            // This is the case where the first number does not have a country code.
            // examples:
            // (123) 456-7890   &   123-456-7890  (collapse)
            // 0049 (8092) 1234   &   +49/80921234  (unit test says do not collapse)

            // Check the second number.  If it also does not have a country code, then
            // we should collapse.  If it has a country code, then it's a different
            // number and we should not collapse (this conclusion is based on an
            // existing unit test).
            try {
              util.parse(dataPart2, null);
            } catch (NumberParseException e2) {
              // Number 2 also does not have a country.  Collapse.
              break;
            }
          }
          return false;
        case SHORT_NSN_MATCH:
          return false;
        default:
          throw new IllegalStateException("Unknown result value from phone number " + "library");
      }
    }
    return true;
  }

  /**
   * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates that
   * are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to how the
   * target {@link android.graphics.Rect} is calculated in {@link
   * android.provider.ContactsContract.QuickContact#showQuickContact}.
   */
  public static Rect getTargetRectFromView(View view) {
    final int[] pos = new int[2];
    view.getLocationOnScreen(pos);

    final Rect rect = new Rect();
    rect.left = pos[0];
    rect.top = pos[1];
    rect.right = pos[0] + view.getWidth();
    rect.bottom = pos[1] + view.getHeight();
    return rect;
  }

  /**
   * Returns the intent to launch for the given invitable account type and contact lookup URI. This
   * will return null if the account type is not invitable (i.e. there is no {@link
   * AccountType#getInviteContactActivityClassName()} or {@link
   * AccountType#syncAdapterPackageName}).
   */
  public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) {
    String syncAdapterPackageName = accountType.syncAdapterPackageName;
    String className = accountType.getInviteContactActivityClassName();
    if (TextUtils.isEmpty(syncAdapterPackageName) || TextUtils.isEmpty(className)) {
      return null;
    }
    Intent intent = new Intent();
    intent.setClassName(syncAdapterPackageName, className);

    intent.setAction(ContactsContract.Intents.INVITE_CONTACT);

    // Data is the lookup URI.
    intent.setData(lookupUri);
    return intent;
  }
}
