/**
 * Implements <tt>ContactSourceService</tt> for Google Contacts.
 *
 * @author Sebastien Vincent
 */
public class GoogleContactsSourceService implements ExtendedContactSourceService {
  /** Logger. */
  private static final Logger logger = Logger.getLogger(GoogleContactsSourceService.class);

  /**
   * The <tt>List</tt> of <tt>GoogleContactsQuery</tt> instances which have been started and haven't
   * stopped yet.
   */
  private final List<GoogleContactsQuery> queries = new LinkedList<GoogleContactsQuery>();

  /** Login. */
  private final String login;

  /** Password. */
  private final String password;

  /** The prefix for all google contact phone numbers. */
  private String phoneNumberprefix;

  /** Google Contacts connection. */
  private GoogleContactsConnection cnx = null;

  /** The account settings form. */
  private AccountSettingsForm settings = null;

  /** If the account has been created using GoogleTalk wizard or via external Google Contacts. */
  private boolean googleTalk = false;

  /**
   * Constructor.
   *
   * @param login login
   * @param password password
   */
  public GoogleContactsSourceService(String login, String password) {
    super();
    this.login = login;
    this.password = password;
  }

  /**
   * Constructor.
   *
   * @param cnx connection
   */
  public GoogleContactsSourceService(GoogleContactsConnection cnx) {
    super();
    this.cnx = cnx;
    this.login = cnx.getLogin();
    this.password = cnx.getPassword();
    this.phoneNumberprefix = cnx.getPrefix();
  }

  /**
   * Returns login.
   *
   * @return login
   */
  public String getLogin() {
    return login;
  }

  /**
   * Set whether or not the account has been created via GoogleTalk wizard or external Google
   * contacts.
   *
   * @param googleTalk value to set
   */
  public void setGoogleTalk(boolean googleTalk) {
    this.googleTalk = googleTalk;
  }

  /**
   * Returns whether or not the account has been created via GoogleTalk wizard or via external
   * Google Contacts.
   *
   * @return true if account has been created via GoogleTalk wizard or via external Google Contacts.
   */
  public boolean isGoogleTalk() {
    return googleTalk;
  }

  /**
   * Creates query for the given <tt>searchPattern</tt>.
   *
   * @param queryPattern the pattern to search for
   * @return the created query
   */
  public ContactQuery createContactQuery(Pattern queryPattern) {
    return createContactQuery(queryPattern, GoogleContactsQuery.GOOGLECONTACTS_MAX_RESULTS);
  }

  /**
   * Creates query for the given <tt>searchPattern</tt>.
   *
   * @param queryPattern the pattern to search for
   * @param count maximum number of contact returned
   * @return the created query
   */
  public ContactQuery createContactQuery(Pattern queryPattern, int count) {
    GoogleContactsQuery query = new GoogleContactsQuery(this, queryPattern, count);

    synchronized (queries) {
      queries.add(query);
    }

    return query;
  }

  /**
   * Removes query from the list of queries.
   *
   * @param query the query that will be removed.
   */
  public synchronized void removeQuery(ContactQuery query) {
    if (queries.remove(query)) queries.notify();
  }

  /**
   * Returns the Google Contacts connection.
   *
   * @return Google Contacts connection
   */
  public GoogleContactsConnectionImpl getConnection() {
    int s = login.indexOf('@');
    boolean isGoogleAppsOrGmail = false;

    if (s == -1) {
      return null;
    }

    String domain = login.substring((s + 1));

    try {
      SRVRecord srvRecords[] = NetworkUtils.getSRVRecords("xmpp-client", "tcp", domain);

      if (srvRecords != null) {
        // To detect that account is a google ones, we try following:
        // - lookup in SRV and see if it is google.com;
        // - if the account has been created with GoogleTalk form;
        // - if it is an "external" google contact.

        // SRV checks
        for (SRVRecord srv : srvRecords) {
          if (srv.getTarget().endsWith("google.com") || srv.getTarget().endsWith("google.com.")) {
            isGoogleAppsOrGmail = true;
            break;
          }
        }
      }

      // GoogleTalk based account or external Google Contacts ?
      if (!isGoogleAppsOrGmail) {
        isGoogleAppsOrGmail = googleTalk;
      }

      if (isGoogleAppsOrGmail) {
        if (cnx == null) {
          cnx = new GoogleContactsConnectionImpl(login, password);

          if (cnx.connect()
              == GoogleContactsConnection.ConnectionStatus.ERROR_INVALID_CREDENTIALS) {
            synchronized (this) {
              if (settings != null) {
                cnx = null;
                return null;
              } else {
                settings = new AccountSettingsForm();
              }
            }
            settings.setModal(true);
            settings.loadData(cnx);
            int ret = settings.showDialog();

            if (ret == 1) {
              cnx = settings.getConnection();
              GoogleContactsActivator.getGoogleContactsService().saveConfig(cnx);
            } else {
              cnx = null;
            }
          }
        } else if (cnx.connect()
            == GoogleContactsConnection.ConnectionStatus.ERROR_INVALID_CREDENTIALS) {
          synchronized (this) {
            if (settings != null) {
              cnx = null;
              return null;
            } else {
              settings = new AccountSettingsForm();
            }
          }
          settings.setModal(true);
          settings.loadData(cnx);
          int ret = settings.showDialog();

          if (ret == 1) {
            cnx = settings.getConnection();
            GoogleContactsActivator.getGoogleContactsService().saveConfig(cnx);
          } else {
            cnx = null;
          }
        }
      } else {
        cnx = null;
      }
    } catch (Exception e) {
      logger.info("GoogleContacts connection error", e);
      return null;
    }

    return (GoogleContactsConnectionImpl) cnx;
  }

  /**
   * Returns a user-friendly string that identifies this contact source.
   *
   * @return the display name of this contact source
   */
  public String getDisplayName() {
    return login;
  }

  /**
   * Returns SEARCH_TYPE to indicate that this contact source
   *
   * @return the identifier of this contact source
   */
  public int getType() {
    return SEARCH_TYPE;
  }

  /**
   * Queries this search source for the given <tt>queryString</tt>.
   *
   * @param query the string to search for
   * @return the created query
   */
  public ContactQuery createContactQuery(String query) {
    return createContactQuery(query, GoogleContactsQuery.GOOGLECONTACTS_MAX_RESULTS);
  }

  /**
   * Creates query for the given <tt>queryString</tt>.
   *
   * @param query the string to search for
   * @param contactCount the maximum count of result contacts
   * @return the created query
   */
  public ContactQuery createContactQuery(String query, int contactCount) {
    Pattern pattern = null;
    try {
      pattern = Pattern.compile(query);
    } catch (PatternSyntaxException pse) {
      pattern = Pattern.compile(Pattern.quote(query));
    }

    if (pattern != null) {
      return createContactQuery(pattern, contactCount);
    }
    return null;
  }

  /**
   * Stops this <tt>ContactSourceService</tt> implementation and prepares it for garbage collection.
   *
   * @see AsyncContactSourceService#stop()
   */
  public void stop() {
    boolean interrupted = false;

    synchronized (queries) {
      while (!queries.isEmpty()) {
        queries.get(0).cancel();
        try {
          queries.wait();
        } catch (InterruptedException iex) {
          interrupted = true;
        }
      }
    }
    if (interrupted) Thread.currentThread().interrupt();
  }

  /**
   * Notifies this <tt>GoogleContactsSourceService</tt> that a specific <tt>GoogleContactsQuery</tt>
   * has stopped.
   *
   * @param query the <tt>GoogleContactsQuery</tt> which has stopped
   */
  void stopped(GoogleContactsQuery query) {
    synchronized (queries) {
      if (queries.remove(query)) queries.notify();
    }
  }

  /**
   * Returns the phoneNumber prefix for all phone numbers.
   *
   * @return the phoneNumber prefix for all phone numbers
   */
  public String getPhoneNumberPrefix() {
    return phoneNumberprefix;
  }

  /**
   * Sets the phone number prefix.
   *
   * @param phoneNumberprefix the phone number prefix to set
   */
  public void setPhoneNumberPrefix(String phoneNumberprefix) {
    this.phoneNumberprefix = phoneNumberprefix;
  }

  /**
   * Returns the index of the contact source in the result list.
   *
   * @return the index of the contact source in the result list
   */
  public int getIndex() {
    return -1;
  }
}
  /**
   * Returns the Google Contacts connection.
   *
   * @return Google Contacts connection
   */
  public GoogleContactsConnectionImpl getConnection() {
    int s = login.indexOf('@');
    boolean isGoogleAppsOrGmail = false;

    if (s == -1) {
      return null;
    }

    String domain = login.substring((s + 1));

    try {
      SRVRecord srvRecords[] = NetworkUtils.getSRVRecords("xmpp-client", "tcp", domain);

      if (srvRecords != null) {
        // To detect that account is a google ones, we try following:
        // - lookup in SRV and see if it is google.com;
        // - if the account has been created with GoogleTalk form;
        // - if it is an "external" google contact.

        // SRV checks
        for (SRVRecord srv : srvRecords) {
          if (srv.getTarget().endsWith("google.com") || srv.getTarget().endsWith("google.com.")) {
            isGoogleAppsOrGmail = true;
            break;
          }
        }
      }

      // GoogleTalk based account or external Google Contacts ?
      if (!isGoogleAppsOrGmail) {
        isGoogleAppsOrGmail = googleTalk;
      }

      if (isGoogleAppsOrGmail) {
        if (cnx == null) {
          cnx = new GoogleContactsConnectionImpl(login, password);

          if (cnx.connect()
              == GoogleContactsConnection.ConnectionStatus.ERROR_INVALID_CREDENTIALS) {
            synchronized (this) {
              if (settings != null) {
                cnx = null;
                return null;
              } else {
                settings = new AccountSettingsForm();
              }
            }
            settings.setModal(true);
            settings.loadData(cnx);
            int ret = settings.showDialog();

            if (ret == 1) {
              cnx = settings.getConnection();
              GoogleContactsActivator.getGoogleContactsService().saveConfig(cnx);
            } else {
              cnx = null;
            }
          }
        } else if (cnx.connect()
            == GoogleContactsConnection.ConnectionStatus.ERROR_INVALID_CREDENTIALS) {
          synchronized (this) {
            if (settings != null) {
              cnx = null;
              return null;
            } else {
              settings = new AccountSettingsForm();
            }
          }
          settings.setModal(true);
          settings.loadData(cnx);
          int ret = settings.showDialog();

          if (ret == 1) {
            cnx = settings.getConnection();
            GoogleContactsActivator.getGoogleContactsService().saveConfig(cnx);
          } else {
            cnx = null;
          }
        }
      } else {
        cnx = null;
      }
    } catch (Exception e) {
      logger.info("GoogleContacts connection error", e);
      return null;
    }

    return (GoogleContactsConnectionImpl) cnx;
  }