/*
 * Copyright (C) 2012 Canonical, Ltd.
 *
 * Authors:
 *  Renato Araujo Oliveira Filho <renato@canonical.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
using Dee;
using Gee;
using Config;
using Folks;

namespace Unity.ContactsLens {
    const string BUS_NAME = "com.canonical.Unity.Lens.Contacts";
    const string BUS_PATH = "/com/canonical/unity/lens/contacts";
    const string CONTACT_SCHEME = "contact://";

    /**
    * The Daemon class implements all of the logic for the lens.
    */
    public class Daemon : GLib.Object
    {
        private Unity.Lens lens;
        private Unity.Scope scope;
        private Folks.IndividualAggregator aggregator;

        private const string CATEGORY_FAVORITES = "Favorites";
        private const string CATEGORY_RECENTS = "Recents";

        private const string ICONS_DIR = Config.LENSESDIR + "/contacts/";
        private const string ICON_FAVORITE_FILE = Config.PREFIX + "/share/icons/unity-icon-theme/places/svg/group-mostused.svg";

        construct
        {
            /* Set up the lens object (make sure it matches info in the .lens file */
            lens = new Unity.Lens (BUS_PATH, "contacts");
            lens.search_hint = _("Search contacts");
            lens.visible = true;
            lens.search_in_global = true;
            lens.filters = null;

            populate_categories ();
            initialize_folks ();

            /* Create a scope so we can add results to the lens */
            scope = new Unity.Scope ("/com/canonical/unity/lens/contacts/folksscope");

            /* Let's ignore searches where only the leading or trailing whitespace
            * changes */
            scope.generate_search_key.connect ((lens_search) =>
            {
                return lens_search.search_string.strip ();
            });
            /* Listen for search changes */
            scope.search_changed.connect (on_search_changed);

            /* Register the scope we created as local */
            lens.add_local_scope (scope);

            /* Export the lens on the bus. Unity can see us past this point */
            try {
                lens.export ();
            } catch (IOError error) {
                critical ("Failed to export DBus service for '%s': %s",
                          lens.dbus_path, error.message);
            }
        }

        private void populate_categories ()
        {
            GLib.List<Unity.Category> categories = new GLib.List<Unity.Category> ();
            var icons_dir = File.new_for_path (ICONS_DIR);
            var icon_favorites = File.new_for_path (ICON_FAVORITE_FILE);

            /* The offsets should match those from the Category enum (schemas.vala) */
            var cat = new Unity.Category (_("Favorites"),
                                           new FileIcon (icon_favorites),
                                           Unity.CategoryRenderer.HORIZONTAL_TILE);
            categories.append (cat);

            cat = new Unity.Category (_("Recent Calls"),
                                      new FileIcon (icons_dir.get_child ("contacts-lens-recent-call.svg")),
                                      Unity.CategoryRenderer.HORIZONTAL_TILE);
            categories.append (cat);

            cat = new Unity.Category (_("A-Z"),
                                      new FileIcon (icons_dir.get_child ("contacts-lens-a-z.svg")),
                                      Unity.CategoryRenderer.HORIZONTAL_TILE);
            categories.append (cat);


            lens.categories = categories;
        }

        private void initialize_folks ()
        {
            aggregator = new Folks.IndividualAggregator();
            aggregator.prepare();
        }

        private void on_search_changed (Unity.Scope scope,
                                        Unity.LensSearch search,
                                        Unity.SearchType search_type,
                                        Cancellable cancellable)
        {
           start_search.begin (search, search_type, cancellable);
        }

        private HashSet<Folks.Individual> folks_search (string search, Cancellable cancellable)
        {
            var results = new HashSet<Folks.Individual>();
            var contacts = aggregator.individuals.values;

            foreach (var contact in contacts) {
                if (cancellable.is_cancelled ()) {
                    return results;
                }

                // skip self contact
                if (contact.is_user) {
                    continue;
                }

                if (search.length == 0) {
                    results.add(contact);
                    continue;
                }

                // Check name, nickname
                if ((search in contact.nickname.down()) ||
                    (search in contact.alias.down()) ||
                    (search in contact.full_name.down())) {
                    results.add(contact);
                    continue;
                }

                // Check for e-mail
                foreach(var email in contact.email_addresses) {
                    if (search in email.value.down()) {
                        results.add(contact);
                        continue;
                    }
                }

                // Check for phone number
                foreach(var phone in contact.phone_numbers) {
                    if (search in phone.value.down()) {
                        results.add(contact);
                        continue;
                    }
                }

                // Check for IM
                foreach(var im in contact.im_addresses.get_values()) {
                    if (search in im.value.down()) {
                        results.add(contact);
                        continue;
                    }
                }

                // Check for Address
                foreach(var addressDetails in contact.postal_addresses) {
                    var address = addressDetails.value;
                    if (search in address.country.down() ||
                        search in address.extension.down() ||
                        search in address.locality.down() ||
                        search in address.po_box.down() ||
                        search in address.postal_code.down() ||
                        search in address.region.down() ||
                        search in address.street.down()) {
                        results.add(contact);
                        continue;
                    }
                }
            }
            return results;
        }

        private async void start_search (Unity.LensSearch search,
                                         Unity.SearchType search_type,
                                         Cancellable cancellable)
        {
            message ("Started search for %s", search.search_string);

            var model = search.results_model;
            var results = folks_search (search.search_string.down(), cancellable);
            var cache = Folks.AvatarCache.dup ();
            var az_count = 0;

            model.clear ();

            foreach (var folk in results) {
                if (cancellable.is_cancelled ()) {
                    /* we got another search meanwhile, let's just end this one */
                    search.finished ();
                    return;
                }

                var category = Category.ALL;
                if (CATEGORY_RECENTS in folk.groups) {
                    category = Category.RECENT_CALLS;
                } else if (folk.is_favourite ||
                    (CATEGORY_FAVORITES in folk.groups)) {
                    category = Category.FAVORITES;
                }

                if (category == Category.ALL && search.search_string.length == 0) {
                    continue;
                }

                if (category == Category.ALL) {
                    az_count += 1;
                    // no more than 50 contacts
                    if (az_count > 50) {
                        continue;
                    }
                }

                var avatar = ICONS_DIR + "/contacts-lens-default.png";
                if (folk.avatar != null) {
                    cache.store_avatar(folk.id, folk.avatar);
                    var avatarUri = cache.build_uri_for_avatar(folk.id);
                    var file = File.new_for_uri (avatarUri);
                    avatar = file.get_path();
                }

                var displayName = _("unknown");
                if (folk.nickname.length > 0) {
                    displayName = folk.nickname;
                } else if (folk.full_name.length > 0) {
                    displayName = folk.full_name;
                } else if (folk.alias.length > 0) {
                    displayName = folk.alias;
                }

                var comments = "";
                if (folk.phone_numbers.size > 0) {
                    var phone_number = folk.phone_numbers.to_array()[0].value;
                    comments += phone_number;
                }

                if (folk.email_addresses.size > 0) {
                    var email_address = folk.email_addresses.to_array()[0].value;
                    comments += "\n" + email_address;
                }

                model.append (CONTACT_SCHEME + folk.id, // URI of the result
                              avatar,                 // string representation of an icon
                              category,               // category index
                              "application/contact",  // mime-type of the URI,
                              displayName,            // display name of the result
                              comments,               // comment
                              "");                    // URI for drag and drop
            }

            search.finished ();
        }
    } /* End Daemon class */

} /* End Unity.ContactsLens namespace */
