/*
 * Copyright (C) 2017 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.dialer.databasepopulator;

import android.content.ContentProviderOperation;
import android.content.Context;
import android.content.OperationApplicationException;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import com.android.dialer.common.Assert;
import com.google.auto.value.AutoValue;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** Populates the device database with contacts. */
public final class ContactsPopulator {
  // Phone numbers from https://www.google.com/about/company/facts/locations/
  private static final Contact[] SIMPLE_CONTACTS = {
    // US, contact with e164 number.
    Contact.builder()
        .setName("Michelangelo")
        .addPhoneNumber(new PhoneNumber("+1-302-6365454", Phone.TYPE_MOBILE))
        .addEmail(new Email("m@example.com"))
        .setIsStarred(true)
        .setPinned(1)
        .setOrangePhoto()
        .build(),
    // US, contact with a non-e164 number.
    Contact.builder()
        .setName("Leonardo da Vinci")
        .addPhoneNumber(new PhoneNumber("(425) 739-5600", Phone.TYPE_MOBILE))
        .addEmail(new Email("l@example.com"))
        .setIsStarred(true)
        .setPinned(2)
        .setBluePhoto()
        .build(),
    // UK, number where the (0) should be dropped.
    Contact.builder()
        .setName("Raphael")
        .addPhoneNumber(new PhoneNumber("+44 (0) 20 7031 3000", Phone.TYPE_MOBILE))
        .addEmail(new Email("r@example.com"))
        .setIsStarred(true)
        .setPinned(3)
        .setRedPhoto()
        .build(),
    // US and Australia, contact with a long name and multiple phone numbers.
    Contact.builder()
        .setName("Donatello di Niccolò di Betto Bardi")
        .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME))
        .addPhoneNumber(new PhoneNumber("+1 404-487-9000", Phone.TYPE_WORK))
        .addPhoneNumber(new PhoneNumber("+61 2 9374 4001", Phone.TYPE_FAX_HOME))
        .setIsStarred(true)
        .setPinned(4)
        .setPurplePhoto()
        .build(),
    // US, phone number shared with another contact and 2nd phone number with wait and pause.
    Contact.builder()
        .setName("Splinter")
        .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME))
        .addPhoneNumber(new PhoneNumber("+1 303-245-0086;123,456", Phone.TYPE_WORK))
        .setBluePhoto()
        .build(),
    // France, number with Japanese name.
    Contact.builder()
        .setName("スパイク・スピーゲル")
        .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE))
        .setBluePhoto()
        .build(),
    // Israel, RTL name and non-e164 number.
    Contact.builder()
        .setName("עקב אריה טברסק")
        .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE))
        .setBluePhoto()
        .build(),
    // UAE, RTL name.
    Contact.builder()
        .setName("سلام دنیا")
        .addPhoneNumber(new PhoneNumber("+971 4 4509500", Phone.TYPE_MOBILE))
        .setBluePhoto()
        .build(),
    // Brazil, contact with no name.
    Contact.builder()
        .addPhoneNumber(new PhoneNumber("+55-31-2128-6800", Phone.TYPE_MOBILE))
        .setBluePhoto()
        .build(),
    // Short number, contact with no name.
    Contact.builder().addPhoneNumber(new PhoneNumber("611", Phone.TYPE_MOBILE)).build(),
    // US, number with an anonymous prefix.
    Contact.builder()
        .setName("Anonymous")
        .addPhoneNumber(new PhoneNumber("*86 512-343-5283", Phone.TYPE_MOBILE))
        .setBluePhoto()
        .build(),
    // None, contact with no phone number.
    Contact.builder()
        .setName("No Phone Number")
        .addEmail(new Email("no@example.com"))
        .setIsStarred(true)
        .setBluePhoto()
        .build(),
  };

  @WorkerThread
  public static void populateContacts(@NonNull Context context, boolean fastMode) {
    Assert.isWorkerThread();
    ArrayList<ContentProviderOperation> operations = new ArrayList<>();
    List<Contact> contacts = new ArrayList<>();
    if (fastMode) {
      contacts.add(SIMPLE_CONTACTS[0]);
    } else {
      contacts = Arrays.asList(SIMPLE_CONTACTS);
    }
    for (Contact contact : contacts) {
      addContact(contact, operations);
    }

    try {
      context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
    } catch (RemoteException | OperationApplicationException e) {
      Assert.fail("error adding contacts: " + e);
    }
  }

  @WorkerThread
  public static void populateSpeedDialTestContacts(@NonNull Context context) {
    Assert.isWorkerThread();
    ArrayList<ContentProviderOperation> operations = new ArrayList<>();
    addContact(SIMPLE_CONTACTS[0], operations);
    addContact(SIMPLE_CONTACTS[3], operations);
    addContact(SIMPLE_CONTACTS[5], operations);
    try {
      context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
    } catch (RemoteException | OperationApplicationException e) {
      Assert.fail("error adding contacts: " + e);
    }
  }

  @WorkerThread
  public static void populateContacts(@NonNull Context context) {
    populateContacts(context, false);
  }

  @WorkerThread
  public static void deleteAllContacts(@NonNull Context context) {
    Assert.isWorkerThread();
    try {
      context
          .getContentResolver()
          .applyBatch(
              ContactsContract.AUTHORITY,
              new ArrayList<>(
                  Arrays.asList(
                      ContentProviderOperation.newDelete(RawContacts.CONTENT_URI).build())));
    } catch (RemoteException | OperationApplicationException e) {
      Assert.fail("failed to delete contacts: " + e);
    }
  }

  private static void addContact(Contact contact, List<ContentProviderOperation> operations) {
    int index = operations.size();

    operations.add(
        ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, contact.getAccountType())
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.getAccountName())
            .withValue(ContactsContract.RawContacts.STARRED, contact.getIsStarred() ? 1 : 0)
            .withValue(
                ContactsContract.RawContacts.PINNED,
                contact.getIsStarred() ? contact.getPinned() : 0)
            .withYieldAllowed(true)
            .build());

    if (!TextUtils.isEmpty(contact.getName())) {
      operations.add(
          ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
              .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
              .withValue(
                  ContactsContract.Data.MIMETYPE,
                  ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
              .withValue(
                  ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contact.getName())
              .build());
    }

    if (contact.getPhotoStream() != null) {
      operations.add(
          ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
              .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
              .withValue(
                  ContactsContract.Data.MIMETYPE,
                  ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
              .withValue(
                  ContactsContract.CommonDataKinds.Photo.PHOTO,
                  contact.getPhotoStream().toByteArray())
              .build());
    }

    for (PhoneNumber phoneNumber : contact.getPhoneNumbers()) {
      operations.add(
          ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
              .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
              .withValue(
                  ContactsContract.Data.MIMETYPE,
                  ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
              .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber.value)
              .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneNumber.type)
              .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.label)
              .build());
    }

    for (Email email : contact.getEmails()) {
      operations.add(
          ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
              .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
              .withValue(
                  ContactsContract.Data.MIMETYPE,
                  ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
              .withValue(ContactsContract.CommonDataKinds.Email.DATA, email.value)
              .withValue(ContactsContract.CommonDataKinds.Email.TYPE, email.type)
              .withValue(ContactsContract.CommonDataKinds.Email.LABEL, email.label)
              .build());
    }
  }

  @AutoValue
  abstract static class Contact {
    @NonNull
    abstract String getAccountType();

    @NonNull
    abstract String getAccountName();

    @Nullable
    abstract String getName();

    abstract boolean getIsStarred();

    abstract int getPinned();

    @Nullable
    abstract ByteArrayOutputStream getPhotoStream();

    @NonNull
    abstract List<PhoneNumber> getPhoneNumbers();

    @NonNull
    abstract List<Email> getEmails();

    static Builder builder() {
      return new AutoValue_ContactsPopulator_Contact.Builder()
          .setAccountType("com.google")
          .setAccountName("foo@example")
          .setPinned(0)
          .setIsStarred(false)
          .setPhoneNumbers(new ArrayList<>())
          .setEmails(new ArrayList<>());
    }

    @AutoValue.Builder
    abstract static class Builder {
      @NonNull private final List<PhoneNumber> phoneNumbers = new ArrayList<>();
      @NonNull private final List<Email> emails = new ArrayList<>();

      abstract Builder setAccountType(@NonNull String accountType);

      abstract Builder setAccountName(@NonNull String accountName);

      abstract Builder setName(@NonNull String name);

      abstract Builder setIsStarred(boolean isStarred);

      abstract Builder setPinned(int position);

      abstract Builder setPhotoStream(ByteArrayOutputStream photoStream);

      abstract Builder setPhoneNumbers(@NonNull List<PhoneNumber> phoneNumbers);

      abstract Builder setEmails(@NonNull List<Email> emails);

      abstract Contact build();

      Builder addPhoneNumber(PhoneNumber phoneNumber) {
        phoneNumbers.add(phoneNumber);
        return setPhoneNumbers(phoneNumbers);
      }

      Builder addEmail(Email email) {
        emails.add(email);
        return setEmails(emails);
      }

      Builder setRedPhoto() {
        setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xe3, 0x33, 0x1c)));
        return this;
      }

      Builder setBluePhoto() {
        setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x00, 0xaa, 0xe6)));
        return this;
      }

      Builder setOrangePhoto() {
        setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xea, 0x95, 0x00)));
        return this;
      }

      Builder setPurplePhoto() {
        setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x99, 0x5a, 0xa0)));
        return this;
      }

      /** Creates a contact photo with a green background and a circle of the given color. */
      private static ByteArrayOutputStream getPhotoStreamWithColor(int color) {
        int width = 300;
        int height = 300;
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawColor(Color.argb(0xff, 0x4c, 0x9c, 0x23));
        Paint paint = new Paint();
        paint.setColor(color);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(width / 2, height / 2, width / 3, paint);

        ByteArrayOutputStream photoStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 75, photoStream);
        return photoStream;
      }
    }
  }

  static class PhoneNumber {
    public final String value;
    public final int type;
    public final String label;

    PhoneNumber(String value, int type) {
      this.value = value;
      this.type = type;
      label = "simulator phone number";
    }
  }

  static class Email {
    public final String value;
    public final int type;
    public final String label;

    Email(String simpleEmail) {
      value = simpleEmail;
      type = ContactsContract.CommonDataKinds.Email.TYPE_WORK;
      label = "simulator email";
    }
  }

  private ContactsPopulator() {}
}
