/*
 * Copyright (C) 2011, 2012 Philip Withnall
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *       Philip Withnall <philip@tecnocode.co.uk>
 */

using Gee;
using GLib;

/**
 * Interface for {@link Persona} subclasses from backends which support storage
 * of, anti-linking data.
 *
 * Anti-links are stored as a set of {@link Persona.uid}s with each
 * {@link Persona} (A), specifying that A must not be linked into an
 * {@link Individual} with any of the personas in its anti-links set.
 *
 * @since 0.7.3
 */
public interface Folks.AntiLinkable : Folks.Persona
{
  /**
   * UIDs of anti-linked {@link Persona}s.
   *
   * The {@link Persona}s identified by their UIDs in this set are guaranteed to
   * not be linked to this {@link Persona}, even if their linkable properties
   * match.
   *
   * No UIDs may be ``null``. Well-formed but non-existent UIDs (i.e. UIDs which
   * can be successfully parsed, but which don't currently correspond to a
   * {@link Persona} instance) are permitted, as personas may appear and
   * disappear over time.
   *
   * It is expected, but not guaranteed, that anti-links made between personas
   * will be reciprocal. That is, if persona A lists persona B's UID in its
   * {@link AntiLinkable.anti_links} set, persona B will typically also list
   * persona A in its anti-links set.
   *
   * @since 0.7.3
   */
  public abstract Set<string> anti_links { get; set; }

  /**
   * Change the {@link Persona}'s set of anti-links.
   *
   * It's preferred to call this rather than setting
   * {@link AntiLinkable.anti_links} directly, as this method gives error
   * notification and will only return once the anti-links have been written
   * to the relevant backing store (or the operation's failed).
   *
   * It should be noted that {@link IndividualAggregator.link_personas} and
   * {@link IndividualAggregator.unlink_individual} will modify the anti-links
   * sets of the personas they touch, in order to remove and add anti-links,
   * respectively. It is expected that these {@link IndividualAggregator}
   * methods will be used to modify anti-links indirectly, rather than calling
   * {@link AntiLinkable.change_anti_links} directly.
   *
   * @param anti_links the new set of anti-links from this persona
   * @throws PropertyError if setting the anti-links failed
   * @since 0.7.3
   */
  public virtual async void change_anti_links (Set<string> anti_links)
      throws PropertyError
    {
      /* Default implementation. */
      throw new PropertyError.NOT_WRITEABLE (
          _("Anti-links are not writeable on this contact."));
    }

  /**
   * Check for an anti-link with another persona.
   *
   * This will return ``true`` if ``other_persona``'s UID is listed in this
   * persona's anti-links set. Note that this check is not symmetric.
   *
   * @param other_persona the persona to check is anti-linked
   * @return ``true`` if an anti-link exists, ``false`` otherwise
   * @since 0.7.3
   */
  public bool has_anti_link_with_persona (Persona other_persona)
    {
      return (other_persona.uid in this.anti_links);
    }

  /**
   * Add anti-links to other personas.
   *
   * The UIDs of all personas in ``other_personas`` will be added to this
   * persona's anti-links set and the changes propagated to backends.
   *
   * Any attempt to anti-link a persona with itself is not an error, but is
   * ignored.
   *
   * @param other_personas the personas to anti-link to this one
   * @throws PropertyError if setting the anti-links failed
   * @since 0.7.3
   */
  public async void add_anti_links (Set<Persona> other_personas)
      throws PropertyError
    {
      var new_anti_links = new HashSet<string> ();
      new_anti_links.add_all (this.anti_links);

      foreach (var p in other_personas)
        {
          /* Don't anti-link ourselves. */
          if (p == this)
            {
              continue;
            }

          new_anti_links.add (p.uid);
        }

      yield this.change_anti_links (new_anti_links);
    }

  /**
   * Remove anti-links to other personas.
   *
   * The UIDs of all personas in ``other_personas`` will be removed from this
   * persona's anti-links set and the changes propagated to backends.
   *
   * @param other_personas the personas to remove anti-links from this one
   * @throws PropertyError if setting the anti-links failed
   * @since 0.7.3
   */
  public async void remove_anti_links (Set<Persona> other_personas)
      throws PropertyError
    {
      var new_anti_links = new HashSet<string> ();
      new_anti_links.add_all (this.anti_links);

      foreach (var p in other_personas)
        {
          new_anti_links.remove (p.uid);
        }

      yield this.change_anti_links (new_anti_links);
    }
}

/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
