/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * 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 net.java.sip.communicator.impl.googlecontacts;

import java.util.*;
import java.util.regex.*;

import net.java.sip.communicator.service.contactsource.*;
import net.java.sip.communicator.service.googlecontacts.*;
import net.java.sip.communicator.service.protocol.*;

/**
 * Implements <tt>ContactQuery</tt> for Google Contacts.
 *
 * @author Sebastien Vincent
 */
public class GoogleContactsQuery
    extends AsyncContactQuery<GoogleContactsSourceService>
{
    /**
     * Maximum results for Google Contacts query.
     */
    public static final int GOOGLECONTACTS_MAX_RESULTS = 20;

    /**
     * Maximum number of results for this instance.
     */
    private final int count;

    /**
     * The Google query.
     */
    private GoogleQuery gQuery = null;

    /**
     * Initializes a new <tt>GoogleContactsQuery</tt> instance which is to
     * perform a specific <tt>query</tt> on behalf of a specific
     * <tt>contactSource</tt>.
     *
     * @param contactSource the <tt>ContactSourceService</tt> which is to
     * perform the new <tt>ContactQuery</tt> instance
     * @param query the <tt>Pattern</tt> for which <tt>contactSource</tt> is
     * being queried
     * @param count maximum number of results
     */
    protected GoogleContactsQuery(GoogleContactsSourceService
            contactSource, Pattern query, int count)
    {
        super(contactSource, query);
        this.count = count;
    }

    /**
     * Create a <tt>SourceContact</tt> from a <tt>GoogleContactsEntry</tt>.
     *
     * @param entry <tt>GoogleContactsEntry</tt>
     */
    private void onGoogleContactsEntry(GoogleContactsEntry entry)
    {
        String displayName = entry.getFullName();
        if(displayName == null || displayName.length() == 0)
        {
            if((entry.getGivenName() == null ||
                    entry.getGivenName().length() == 0) &&
                    (entry.getFamilyName() == null ||
                            entry.getFamilyName().length() == 0))
            {
                return;
            }

            displayName = entry.getGivenName() + " " +
                entry.getFamilyName();
        }

        List<ContactDetail> contactDetails = getContactDetails(entry);

        if (!contactDetails.isEmpty())
        {
            GenericSourceContact sourceContact
                = new GenericSourceContact(
                        getContactSource(),
                        displayName,
                        contactDetails);

            try
            {
                byte img[] = GoogleContactsServiceImpl.downloadPhoto(
                        ((GoogleContactsEntryImpl)entry).getPhotoLink(),
                        getContactSource().getConnection().
                        getGoogleService());
                sourceContact.setImage(img);
            }
            catch (OutOfMemoryError oome)
            {
                // Ignore it, the image is not vital.
            }

            addQueryResult(sourceContact);
        }
    }

    /**
     * Performs this <tt>AsyncContactQuery</tt> in a background <tt>Thread</tt>.
     *
     * @see AsyncContactQuery#run()
     */
    @Override
    protected void run()
    {
        GoogleContactsServiceImpl service =
            GoogleContactsActivator.getGoogleContactsService();
        gQuery = new GoogleQuery(query);

        GoogleContactsConnection cnx = getContactSource().getConnection();

        if(cnx == null)
        {
            return;
        }

        service.searchContact(
                cnx,
                gQuery,
                count,
                new GoogleEntryCallback()
                {
                    public void callback(GoogleContactsEntry entry)
                    {
                        onGoogleContactsEntry(entry);
                    }
                });
    }

    @Override
    public synchronized void start()
    {
        boolean hasStarted = false;

        try
        {
            super.start();
            hasStarted = true;
        }
        finally
        {
            if (!hasStarted)
            {
                getContactSource().removeQuery(this);
            }
        }
    }
    /**
     * Gets the <tt>contactDetails</tt> to be set on a <tt>SourceContact</tt>.
     *
     * @param entry <tt>GoogleContactsEntry</tt>
     * @return the <tt>contactDetails</tt> to be set on a <tt>SourceContact</tt>
     */
    private List<ContactDetail> getContactDetails(GoogleContactsEntry entry)
    {
        List<ContactDetail> ret = new LinkedList<ContactDetail>();
        List<String> homeMails = entry.getHomeMails();
        List<String> workMails = entry.getWorkMails();
        List<String> mobilePhones = entry.getMobilePhones();
        List<String> homePhones = entry.getHomePhones();
        List<String> workPhones = entry.getWorkPhones();
        Map<String, GoogleContactsEntry.IMProtocol> ims =
            entry.getIMAddresses();
        ContactDetail detail = null;

        for(String mail : homeMails)
        {
            List<Class<? extends OperationSet>> supportedOpSets
                            = new ArrayList<Class<? extends OperationSet>>(1);
            // can be added as contacts
            supportedOpSets.add(OperationSetPersistentPresence.class);

            detail = new ContactDetail(mail,
                    ContactDetail.Category.Email,
                    new ContactDetail.SubCategory[]{
                        ContactDetail.SubCategory.Home});
            detail.setSupportedOpSets(supportedOpSets);
            ret.add(detail);
        }
        for(String mail : workMails)
        {
            List<Class<? extends OperationSet>> supportedOpSets
                            = new ArrayList<Class<? extends OperationSet>>(1);
            // can be added as contacts
            supportedOpSets.add(OperationSetPersistentPresence.class);

            detail = new ContactDetail(mail,
                    ContactDetail.Category.Email,
                    new ContactDetail.SubCategory[]{
                        ContactDetail.SubCategory.Work});
            detail.setSupportedOpSets(supportedOpSets);
            ret.add(detail);
        }

        for(String homePhone : homePhones)
        {
            List<Class<? extends OperationSet>> supportedOpSets
                = new ArrayList<Class<? extends OperationSet>>(2);

            supportedOpSets.add(OperationSetBasicTelephony.class);
            // can be added as contacts
            supportedOpSets.add(OperationSetPersistentPresence.class);
            homePhone = GoogleContactsActivator.getPhoneNumberI18nService()
                .normalize(homePhone);
            detail = new ContactDetail(homePhone,
                    ContactDetail.Category.Phone,
                    new ContactDetail.SubCategory[]{
                        ContactDetail.SubCategory.Home});
            detail.setSupportedOpSets(supportedOpSets);
            ret.add(detail);
        }

        for(String workPhone : workPhones)
        {
            List<Class<? extends OperationSet>> supportedOpSets
                = new ArrayList<Class<? extends OperationSet>>(2);

            supportedOpSets.add(OperationSetBasicTelephony.class);
            // can be added as contacts
            supportedOpSets.add(OperationSetPersistentPresence.class);
            workPhone = GoogleContactsActivator.getPhoneNumberI18nService()
                .normalize(workPhone);
            detail = new ContactDetail(workPhone,
                ContactDetail.Category.Phone,
                new ContactDetail.SubCategory[]{
                    ContactDetail.SubCategory.Work});
            detail.setSupportedOpSets(supportedOpSets);
            ret.add(detail);
        }

        for(String mobilePhone : mobilePhones)
        {
            List<Class<? extends OperationSet>> supportedOpSets
                = new ArrayList<Class<? extends OperationSet>>(2);

            supportedOpSets.add(OperationSetBasicTelephony.class);
            // can be added as contacts
            supportedOpSets.add(OperationSetPersistentPresence.class);
            mobilePhone = GoogleContactsActivator.getPhoneNumberI18nService()
                .normalize(mobilePhone);
            detail = new ContactDetail(mobilePhone,
                ContactDetail.Category.Phone,
                new ContactDetail.SubCategory[]{
                    ContactDetail.SubCategory.Mobile});
            detail.setSupportedOpSets(supportedOpSets);
            ret.add(detail);
        }

        for(Map.Entry<String, GoogleContactsEntry.IMProtocol> im
                : ims.entrySet())
        {
            if(im.getValue() != GoogleContactsEntry.IMProtocol.OTHER)
            {
                ContactDetail.SubCategory imSubCat;
                switch(im.getValue())
                {
                case AIM:
                    imSubCat = ContactDetail.SubCategory.AIM;
                    break;
                case ICQ:
                    imSubCat = ContactDetail.SubCategory.ICQ;
                    break;
                case JABBER:
                    imSubCat = ContactDetail.SubCategory.Jabber;
                    break;
                case SKYPE:
                    imSubCat = ContactDetail.SubCategory.Skype;
                    break;
                case GOOGLETALK:
                    imSubCat = ContactDetail.SubCategory.GoogleTalk;
                    break;
                default:
                    imSubCat = null;
                    break;
                }

                detail
                    = new ContactDetail(
                            im.getKey(),
                            ContactDetail.Category.InstantMessaging,
                            new ContactDetail.SubCategory[] { imSubCat });

                setIMCapabilities(detail, im.getValue());

                // can be added as contacts
                detail.getSupportedOperationSets().add(
                        OperationSetPersistentPresence.class);

                ret.add(detail);
            }
        }

        return ret;
    }

    /**
     * Sets the IM capabilities of a specific <tt>ContactDetail</tt> (e.g.
     * <tt>supportedOpSets</tt>).
     *
     * @param contactDetail the <tt>ContactDetail</tt> to set the capabilities
     * of
     * @param protocol protocol
     * @return <tt>contactDetail</tt>
     */
    private ContactDetail setIMCapabilities(
            ContactDetail contactDetail,
            GoogleContactsEntry.IMProtocol protocol)
    {
        List<Class<? extends OperationSet>> supportedOpSets
            = new LinkedList<Class<? extends OperationSet>>();
        Map<Class<? extends OperationSet>, String> preferredProtocols
            = new HashMap<Class<? extends OperationSet>, String>();

        switch (protocol)
        {
        case GOOGLETALK:
            supportedOpSets.add(OperationSetBasicInstantMessaging.class);
            preferredProtocols.put(
                    OperationSetBasicInstantMessaging.class,
                    ProtocolNames.AIM);
            break;
        case ICQ:
            supportedOpSets.add(OperationSetBasicInstantMessaging.class);
            preferredProtocols.put(
                    OperationSetBasicInstantMessaging.class,
                    ProtocolNames.ICQ);
            break;
        case JABBER:
            supportedOpSets.add(OperationSetBasicInstantMessaging.class);
            preferredProtocols.put(
                    OperationSetBasicInstantMessaging.class,
                    ProtocolNames.JABBER);
            supportedOpSets.add(OperationSetBasicTelephony.class);
            preferredProtocols.put(
                    OperationSetBasicTelephony.class,
                    ProtocolNames.JABBER);
            break;
        default:
            break;
        }
        contactDetail.setSupportedOpSets(supportedOpSets);

        if (!preferredProtocols.isEmpty())
            contactDetail.setPreferredProtocols(preferredProtocols);

        return contactDetail;
    }

    /**
     * Notifies this <tt>GoogleContactsQuery</tt> that it has stopped performing
     * in the associated background <tt>Thread</tt>.
     *
     * @param completed <tt>true</tt> if this <tt>ContactQuery</tt> has
     * successfully completed, <tt>false</tt> if an error has been encountered
     * during its execution
     * @see AsyncContactQuery#stopped(boolean)
     */
    @Override
    protected void stopped(boolean completed)
    {
        try
        {
            super.stopped(completed);
        }
        finally
        {
            getContactSource().stopped(this);
        }
    }

    /**
     * Cancels this <tt>ContactQuery</tt>.
     *
     * @see ContactQuery#cancel()
     */
    @Override
    public void cancel()
    {
        if(gQuery != null)
        {
            gQuery.cancel();
        }
        super.cancel();
    }
}
