package ab.issue.model.values;

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import android.arch.persistence.room.TypeConverter;
import android.arch.persistence.room.TypeConverters;
import android.content.Context;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.google.common.base.MoreObjects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.zxing.client.result.AddressBookParsedResult;

import org.apache.commons.lang3.ArrayUtils;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nonnull;

import ab.issue.db.BarcodeDatabase;
import ab.issue.db.DbConstants;
import ab.issue.model.Barcode;
import androidx.navigation.NavController;
import ezvcard.VCard;
import ezvcard.VCardVersion;
import ezvcard.parameter.TelephoneType;
import ezvcard.property.StructuredName;

import static com.google.common.base.Optional.fromNullable;

@SuppressWarnings("Guava")
@Entity(
        tableName = DbConstants.CONTACT_TABLE_NAME,
        foreignKeys = @ForeignKey(
                entity = Barcode.class,
                parentColumns = DbConstants.COLUMN_NAME_BARCODE_ID,
                childColumns = DbConstants.COLUMN_NAME_CONTACT_BARCODE_ID,
                onUpdate = ForeignKey.CASCADE,
                onDelete = ForeignKey.CASCADE
        ),
        indices = @Index(value = DbConstants.COLUMN_NAME_CONTACT_BARCODE_ID, unique = true)
)
public class ContactInfoBarcodeFormat implements BarcodeFormatValue {

    @TypeConverter
    public static String fromUrlList(List<String> urls) {
        return new Gson().toJson(urls);
    }

    @TypeConverter
    public static List<String> toUrlList(String urls) {
        TypeToken<List<String>> typeToken = new TypeToken<List<String>>() {
        };
        return new Gson().fromJson(urls, typeToken.getType());
    }

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_ID)
    private long id;

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_BARCODE_ID)
    private long barcodeId;

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_TITLE)
    private String title;

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_ORGANIZATION)
    private String organization;

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_NAME)
    @TypeConverters(PersonName.class)
    private PersonName personName;

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_URL)
    @TypeConverters(ContactInfoBarcodeFormat.class)
    private List<String> urls = new ArrayList<>();

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_PHONE)
    @TypeConverters(Phone.class)
    private List<Phone> phones = new ArrayList<>();

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_EMAIL)
    @TypeConverters(Email.class)
    private List<Email> emails = new ArrayList<>();

    @ColumnInfo(name = DbConstants.COLUMN_NAME_CONTACT_ADDRESS)
    @TypeConverters(Address.class)
    private List<Address> addresses = new ArrayList<>();

    public static ContactInfoBarcodeFormat create(@Nullable FirebaseVisionBarcode.ContactInfo contactInfo) {
        ContactInfoBarcodeFormat result = new ContactInfoBarcodeFormat();
        if (contactInfo != null) {
            result.title = contactInfo.getTitle();
            result.organization = contactInfo.getOrganization();
            result.personName = PersonName.create(contactInfo.getName());
            result.urls.addAll(Lists.newArrayList(ArrayUtils.nullToEmpty(contactInfo.getUrls())));

            List<FirebaseVisionBarcode.Address> addresses = fromNullable(contactInfo.getAddresses()).or(new ArrayList<>());
            result.addresses.addAll(Lists.transform(addresses, Address::create));

            List<FirebaseVisionBarcode.Phone> phones = fromNullable(contactInfo.getPhones()).or(new ArrayList<>());
            result.phones.addAll(Lists.transform(phones, Phone::create));

            List<FirebaseVisionBarcode.Email> emails = fromNullable(contactInfo.getEmails()).or(new ArrayList<>());
            result.emails.addAll(Lists.transform(emails, Email::create));
        }
        return result;
    }

    public static ContactInfoBarcodeFormat create(AddressBookParsedResult addressBookParsedResult) {
        ContactInfoBarcodeFormat result = new ContactInfoBarcodeFormat();
        if (addressBookParsedResult != null) {
//            result.personName = PersonName.create(getFirstItem(addressBookParsedResult.getNames()));
            result.title = addressBookParsedResult.getTitle();
            result.organization = addressBookParsedResult.getOrg();
            result.urls.addAll(Lists.newArrayList(ArrayUtils.nullToEmpty(addressBookParsedResult.getURLs())));
            result.addresses.addAll(FluentIterable
                    .from(ArrayUtils.nullToEmpty(addressBookParsedResult.getAddresses()))
                    .transform(Address::create)
                    .toList());
            result.phones.addAll(FluentIterable
                    .from(ArrayUtils.nullToEmpty(addressBookParsedResult.getAddresses()))
                    .transform(Phone::create)
                    .toList());
            result.emails.addAll(FluentIterable
                    .from(ArrayUtils.nullToEmpty(addressBookParsedResult.getEmails()))
                    .transform(Email::create)
                    .toList());
        }
        return result;
    }

    @Override
    public String getName(Context context) {
        return null;
    }

    @Override
    public BarcodeFormatValueType getType() {
        return BarcodeFormatValueType.CONTACT;
    }

    @Nonnull
    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("title", title)
                .add("org", organization)
                .add("addresses", addresses)
                .add("phones", phones)
                .add("emails", emails)
                .add("urls", urls)
                .add("personName", personName)
                .omitNullValues()
                .toString();
    }

    @Override
    public String encode() {
        VCard vCard = new VCard();
        vCard.setVersion(VCardVersion.V2_1);

        if (personName != null) {
            StructuredName structuredName = new StructuredName();
            if (!TextUtils.isEmpty(personName.getLastName())) {
                structuredName.setFamily(personName.getLastName());
            }
            if (!TextUtils.isEmpty(personName.getFirstName())) {
                structuredName.setGiven(personName.getFirstName());
            }
            if (!TextUtils.isEmpty(personName.getMiddleName())) {
                structuredName.getAdditionalNames().add(personName.getMiddleName());
            }
            if (!TextUtils.isEmpty(personName.getPrefix())) {
                structuredName.getPrefixes().add(personName.getPrefix());
            }
            if (!TextUtils.isEmpty(personName.getSuffix())) {
                structuredName.getSuffixes().add(personName.getSuffix());
            }
            vCard.setStructuredName(structuredName);
        }

        if (!TextUtils.isEmpty(title)) {
            vCard.addTitle(title);
        }
        if (!TextUtils.isEmpty(organization)) {
            vCard.setOrganization(organization);
        }

        addTelephoneNumber(vCard, getPhone(PhoneType.HOME), TelephoneType.HOME);
        addTelephoneNumber(vCard, getPhone(PhoneType.WORK), TelephoneType.WORK);
        addTelephoneNumber(vCard, getPhone(PhoneType.MOBILE), TelephoneType.CELL);
        addTelephoneNumber(vCard, getPhone(PhoneType.FAX), TelephoneType.FAX);

        addEmailAddress(vCard, getEmailAddress(Location.HOME), ezvcard.parameter.EmailType.HOME);
        addEmailAddress(vCard, getEmailAddress(Location.WORK), ezvcard.parameter.EmailType.WORK);

        for (String url : urls) {
            vCard.addUrl(url);
        }

        addAddress(vCard, getAddress(Location.WORK), ezvcard.parameter.AddressType.WORK);
        addAddress(vCard, getAddress(Location.HOME), ezvcard.parameter.AddressType.HOME);

        return vCard.write();
    }

    @Override
    public int getIcon() {
        return 0;
    }

    @Override
    public void setBarcodeId(long barcodeId) {
        this.barcodeId = barcodeId;
    }

    @Override
    public void save(BarcodeDatabase barcodeDatabase) {
        barcodeDatabase.contactDao().insert(this);
    }

    @Override
    public void create(NavController navController) {
//        navController.navigate(R.id.action_create_contact_info_barcode);
    }

    @Override
    public List<Barcode> search(BarcodeDatabase barcodeDatabase, String query) {
        return barcodeDatabase.contactDao().findTextInBarcodeFormatValues(query);
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public long getBarcodeId() {
        return barcodeId;
    }

    private void addAddress(VCard vCard,
                            @Nullable Address address,
                            ezvcard.parameter.AddressType addressType) {
        if (address == null || address.getAddressLines().isEmpty()) {
            return;
        }
        ezvcard.property.Address cardAddress = new ezvcard.property.Address();
        cardAddress.getExtendedAddresses().add(address.getAddressLinesSeparatedByNewLine());
        cardAddress.getTypes().add(addressType);
        vCard.addAddress(cardAddress);
    }

    private void addEmailAddress(VCard vCard, @Nullable Email email,
                                 ezvcard.parameter.EmailType emailType) {
        if (email == null || TextUtils.isEmpty(email.getAddress())) {
            return;
        }
        vCard.addEmail(email.getAddress(), emailType);
    }

    private void addTelephoneNumber(VCard vCard, @Nullable Phone phone, TelephoneType telephoneType) {
        if (phone == null || TextUtils.isEmpty(phone.getNumber())) {
            return;
        }
        vCard.addTelephoneNumber(phone.getNumber(), telephoneType);
    }

    public void setLastName(String lastName) {
        checkPersonNameNotNull();
        personName.setLastName(lastName);
    }

    private void checkPersonNameNotNull() {
        if (personName == null) {
            personName = new PersonName();
        }
    }

    public void setFirstName(String firstName) {
        checkPersonNameNotNull();
        personName.setFirstName(firstName);
    }

    public void setMiddleName(String middleName) {
        checkPersonNameNotNull();
        personName.setMiddleName(middleName);
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setOrganization(String organization) {
        this.organization = organization;
    }

    public void setPrefix(String prefix) {
        checkPersonNameNotNull();
        personName.setPrefix(prefix);
    }

    public void setSuffix(String suffix) {
        checkPersonNameNotNull();
        personName.setSuffix(suffix);
    }

    public void setUrl(String url) {
        if (urls.isEmpty()) {
            urls.add(url);
        } else {
            urls.set(0, url);
        }
    }

    public void setHomePhoneNumber(String phoneNumber) {
        setPhoneNumber(phoneNumber, PhoneType.HOME);
    }

    public void setFaxPhoneNumber(String phoneNumber) {
        setPhoneNumber(phoneNumber, PhoneType.FAX);
    }

    public void setWorkPhoneNumber(String phoneNumber) {
        setPhoneNumber(phoneNumber, PhoneType.WORK);
    }

    public void setMobilePhoneNumber(String phoneNumber) {
        setPhoneNumber(phoneNumber, PhoneType.MOBILE);
    }

    @Nullable
    public Phone getPhone(PhoneType phoneType) {
        for (Phone phone : phones) {
            if (phone.getPhoneType() == phoneType) {
                return phone;
            }
        }
        return null;
    }

    private void setPhoneNumber(String phoneNumber, PhoneType phoneType) {
        Phone phone = fromNullable(getPhone(phoneType)).or(new Phone());
        phone.setNumber(phoneNumber);
        if (phone.getPhoneType() == null) {
            phone.setPhoneType(phoneType);
            phones.add(phone);
        }
    }

    @Nullable
    private Email getEmailAddress(Location location) {
        for (Email email : emails) {
            if (email.getLocation() == location) {
                return email;
            }
        }
        return null;
    }

    public void setEmailAddress(String emailAddress, Location location) {
        Email email = fromNullable(getEmailAddress(location)).or(new Email());
        email.setAddress(emailAddress);
        if (email.getLocation() == null) {
            email.setLocation(location);
            emails.add(email);
        }
    }

    @Nullable
    public Address getAddress(Location location) {
        for (Address address : addresses) {
            if (address.getLocation() == location) {
                return address;
            }
        }
        return null;
    }

    public String getTitle() {
        return title;
    }

    public String getOrganization() {
        return organization;
    }

    public PersonName getPersonName() {
        return personName;
    }

    public void setPersonName(PersonName personName) {
        this.personName = personName;
    }

    public List<String> getUrls() {
        return urls;
    }

    public List<Phone> getPhones() {
        return phones;
    }

    public List<Email> getEmails() {
        return emails;
    }

    public List<Address> getAddresses() {
        return addresses;
    }

    public void setUrls(List<String> urls) {
        this.urls = urls;
    }

    public void setEmails(List<Email> emails) {
        this.emails = emails;
    }

    public void setPhones(List<Phone> phones) {
        this.phones = phones;
    }

    public void setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }

    public void setAddress(String address, Location location) {
        Address found = fromNullable(getAddress(location)).or(new Address());
        found.setAddress(address);
        if (found.getLocation() == null) {
            found.setLocation(location);
            addresses.add(found);
        }
    }
}
