// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/autofill/personal_data_manager.h"

#import <AddressBook/AddressBook.h>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/sys_string_conversions.h"
#include "chrome/browser/autofill/autofill_profile.h"
#include "chrome/browser/autofill/phone_number.h"
#include "chrome/common/guid.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util_mac.h"

namespace {

// This implementation makes use of the Address Book API.  Profiles are
// generated that correspond to addresses in the "me" card that reside in the
// user's Address Book.  The caller passes a vector of profiles into the
// the constructer and then initiate the fetch from the Mac Address Book "me"
// card using the main |GetAddressBookMeCard()| method.  This clears any
// existing addresses and populates new addresses derived from the data found
// in the "me" card.
class AuxiliaryProfilesImpl {
 public:
  // Constructor takes a reference to the |profiles| that will be filled in
  // by the subsequent call to |GetAddressBookMeCard()|.  |profiles| may not
  // be NULL.
  explicit AuxiliaryProfilesImpl(ScopedVector<AutofillProfile>* profiles)
      : profiles_(*profiles) {
  }
  virtual ~AuxiliaryProfilesImpl() {}

  // Import the "me" card from the Mac Address Book and fill in |profiles_|.
  void GetAddressBookMeCard();

 private:
  void GetAddressBookNames(ABPerson* me,
                           NSString* addressLabelRaw,
                           AutofillProfile* profile);
  void GetAddressBookAddresses(NSDictionary* address, AutofillProfile* profile);
  void GetAddressBookEmail(ABPerson* me,
                           NSString* addressLabelRaw,
                           AutofillProfile* profile);
  void GetAddressBookPhoneNumbers(ABPerson* me,
                                  NSString* addressLabelRaw,
                                  AutofillProfile* profile);

 private:
  // A reference to the profiles this class populates.
  ScopedVector<AutofillProfile>& profiles_;

  DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesImpl);
};

// This method uses the |ABAddressBook| system service to fetch the "me" card
// from the active user's address book.  It looks for the user address
// information and translates it to the internal list of |AutofillProfile| data
// structures.
void AuxiliaryProfilesImpl::GetAddressBookMeCard() {
  profiles_.reset();

  ABAddressBook* addressBook = [ABAddressBook sharedAddressBook];
  ABPerson* me = [addressBook me];
  if (me) {
    ABMultiValue* addresses = [me valueForProperty:kABAddressProperty];
    for (NSUInteger i = 0, count = [addresses count]; i < count; i++) {
      NSDictionary* address = [addresses valueAtIndex:i];
      NSString* addressLabelRaw = [addresses labelAtIndex:i];

      // Create a new profile where the guid is set to the guid portion of the
      // |kABUIDProperty| taken from from the "me" address.  The format of
      // the |kABUIDProperty| is "<guid>:ABPerson", so we're stripping off the
      // raw guid here and using it directly.
      const size_t kGUIDLength = 36U;
      std::string guid = base::SysNSStringToUTF8(
          [me valueForProperty:kABUIDProperty]).substr(0, kGUIDLength);
      scoped_ptr<AutofillProfile> profile(new AutofillProfile(guid));
      DCHECK(guid::IsValidGUID(profile->guid()));

      // Fill in name and company information.
      GetAddressBookNames(me, addressLabelRaw, profile.get());

      // Fill in address information.
      GetAddressBookAddresses(address, profile.get());

      // Fill in email information.
      GetAddressBookEmail(me, addressLabelRaw, profile.get());

      // Fill in phone and fax number information.
      GetAddressBookPhoneNumbers(me, addressLabelRaw, profile.get());

      profiles_.push_back(profile.release());
    }
  }
}

// Name and company information is stored once in the Address Book against
// multiple addresses.  We replicate that information for each profile.
// We only propagate the company name to work profiles.
void AuxiliaryProfilesImpl::GetAddressBookNames(
    ABPerson* me,
    NSString* addressLabelRaw,
    AutofillProfile* profile) {
  NSString* firstName = [me valueForProperty:kABFirstNameProperty];
  NSString* middleName = [me valueForProperty:kABMiddleNameProperty];
  NSString* lastName = [me valueForProperty:kABLastNameProperty];
  NSString* companyName = [me valueForProperty:kABOrganizationProperty];

  profile->SetInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName));
  profile->SetInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName));
  profile->SetInfo(NAME_LAST, base::SysNSStringToUTF16(lastName));
  if ([addressLabelRaw isEqualToString:kABAddressWorkLabel])
    profile->SetInfo(COMPANY_NAME, base::SysNSStringToUTF16(companyName));
}

// Addresss information from the Address Book may span multiple lines.
// If it does then we represent the address with two lines in the profile.  The
// second line we join with commas.
// For example:  "c/o John Doe\n1122 Other Avenue\nApt #7" translates to
// line 1: "c/o John Doe", line 2: "1122 Other Avenue, Apt #7".
void AuxiliaryProfilesImpl::GetAddressBookAddresses(
    NSDictionary* address,
    AutofillProfile* profile) {
  if (NSString* addressField = [address objectForKey:kABAddressStreetKey]) {
    // If there are newlines in the address, split into two lines.
    if ([addressField rangeOfCharacterFromSet:
            [NSCharacterSet newlineCharacterSet]].location != NSNotFound) {
      NSArray* chunks = [addressField componentsSeparatedByCharactersInSet:
          [NSCharacterSet newlineCharacterSet]];
      DCHECK([chunks count] > 1);

      NSString* separator = l10n_util::GetNSString(
            IDS_AUTOFILL_MAC_ADDRESS_LINE_SEPARATOR);

      NSString* addressField1 = [chunks objectAtIndex:0];
      NSString* addressField2 =
          [[chunks subarrayWithRange:NSMakeRange(1, [chunks count] - 1)]
              componentsJoinedByString:separator];
      profile->SetInfo(ADDRESS_HOME_LINE1,
                       base::SysNSStringToUTF16(addressField1));
      profile->SetInfo(ADDRESS_HOME_LINE2,
                       base::SysNSStringToUTF16(addressField2));
    } else {
      profile->SetInfo(ADDRESS_HOME_LINE1,
                       base::SysNSStringToUTF16(addressField));
    }
  }

  if (NSString* city = [address objectForKey:kABAddressCityKey])
    profile->SetInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city));
  if (NSString* state = [address objectForKey:kABAddressStateKey])
    profile->SetInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state));
  if (NSString* zip = [address objectForKey:kABAddressZIPKey])
    profile->SetInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip));
  if (NSString* country = [address objectForKey:kABAddressCountryKey])
    profile->SetInfo(ADDRESS_HOME_COUNTRY, base::SysNSStringToUTF16(country));
}

// Fills in email address matching current address label.  Note that there may
// be multiple matching email addresses for a given label.  We take the
// first we find (topmost) as preferred.
void AuxiliaryProfilesImpl::GetAddressBookEmail(
    ABPerson* me,
    NSString* addressLabelRaw,
    AutofillProfile* profile) {
  ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty];
  NSString* emailAddress = nil;
  for (NSUInteger j = 0, emailCount = [emailAddresses count];
       j < emailCount; j++) {
    NSString* emailAddressLabelRaw = [emailAddresses labelAtIndex:j];
    if ([emailAddressLabelRaw isEqualToString:addressLabelRaw]) {
      emailAddress = [emailAddresses valueAtIndex:j];
      break;
    }
  }
  profile->SetInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress));
}

// Fills in telephone numbers.  Each of these are special cases.
// We match four cases: home/tel, home/fax, work/tel, work/fax.
// Note, we traverse in reverse order so that top values in address book
// take priority.
void AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers(
    ABPerson* me,
    NSString* addressLabelRaw,
    AutofillProfile* profile) {
  string16 number;
  string16 city_code;
  string16 country_code;

  ABMultiValue* phoneNumbers = [me valueForProperty:kABPhoneProperty];
  for (NSUInteger k = 0, phoneCount = [phoneNumbers count];
       k < phoneCount; k++) {
    NSUInteger reverseK = phoneCount - k - 1;
    NSString* phoneLabelRaw = [phoneNumbers labelAtIndex:reverseK];
    if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] &&
        [phoneLabelRaw isEqualToString:kABPhoneHomeLabel]) {
      string16 homePhone = base::SysNSStringToUTF16(
          [phoneNumbers valueAtIndex:reverseK]);
      PhoneNumber::ParsePhoneNumber(
          homePhone, &number, &city_code, &country_code);
      profile->SetInfo(PHONE_HOME_NUMBER, number);
      profile->SetInfo(PHONE_HOME_CITY_CODE, city_code);
      profile->SetInfo(PHONE_HOME_COUNTRY_CODE, country_code);
    } else if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] &&
               [phoneLabelRaw isEqualToString:kABPhoneHomeFAXLabel]) {
      string16 homeFax = base::SysNSStringToUTF16(
          [phoneNumbers valueAtIndex:reverseK]);
      PhoneNumber::ParsePhoneNumber(homeFax,
          &number, &city_code, &country_code);
      profile->SetInfo(PHONE_FAX_NUMBER, number);
      profile->SetInfo(PHONE_FAX_CITY_CODE, city_code);
      profile->SetInfo(PHONE_FAX_COUNTRY_CODE, country_code);
    } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] &&
               [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) {
      string16 workPhone = base::SysNSStringToUTF16(
          [phoneNumbers valueAtIndex:reverseK]);
      PhoneNumber::ParsePhoneNumber(workPhone,
          &number, &city_code, &country_code);
      profile->SetInfo(PHONE_HOME_NUMBER, number);
      profile->SetInfo(PHONE_HOME_CITY_CODE, city_code);
      profile->SetInfo(PHONE_HOME_COUNTRY_CODE, country_code);
    } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] &&
               [phoneLabelRaw isEqualToString:kABPhoneWorkFAXLabel]) {
      string16 workFax = base::SysNSStringToUTF16(
          [phoneNumbers valueAtIndex:reverseK]);
      PhoneNumber::ParsePhoneNumber(workFax,
          &number, &city_code, &country_code);
      profile->SetInfo(PHONE_FAX_NUMBER, number);
      profile->SetInfo(PHONE_FAX_CITY_CODE, city_code);
      profile->SetInfo(PHONE_FAX_COUNTRY_CODE, country_code);
    }
  }
}

}  // namespace

// Populate |auxiliary_profiles_| with the Address Book data.
void PersonalDataManager::LoadAuxiliaryProfiles() {
  AuxiliaryProfilesImpl impl(&auxiliary_profiles_);
  impl.GetAddressBookMeCard();
}