/*
   * Specify the code you want to run in the sync adapter. The entire
   * sync adapter runs in a background thread, so you don't have to set
   * up your own background processing.
   */
  @Override
  public void onPerformSync(
      Account account,
      Bundle extras,
      String authority,
      ContentProviderClient provider,
      SyncResult syncResult) {

    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    boolean wifiOnly = sharedPreferences.getBoolean("pref_wlan_only", false);
    Log.d(TAG, "Sync only via WIFI? " + wifiOnly);
    boolean hasWifiConnection = NetworkStateHelper.hasWifiConnection(context);
    Log.d(TAG, "Has WIFI connection?" + hasWifiConnection);
    if (!wifiOnly || hasWifiConnection) {
      Log.d(TAG, "*************** SYNCING: " + ++syncNumber + " *****************");
      ServerQueries serverQueries = new ServerQueries(context);
      Cursor c = serverQueries.getAllServers();
      if (c.moveToFirst()) {
        while (!c.isAfterLast()) {
          try {
            getNewNewsForServer(
                c.getLong(ServerQueries.COL_ID),
                c.getString(ServerQueries.COL_NAME),
                c.getInt(ServerQueries.COL_PORT),
                c.getInt(ServerQueries.COL_AUTH) == 1,
                c.getString(ServerQueries.COL_USER),
                c.getString(ServerQueries.COL_PASSWORD));
          } catch (IOException | LoginException e) {
            e.printStackTrace();
          } finally {
            // if we get interrupted during syncing a newsgroup, store date of last message that was
            // fetched in order
            // to start the sync next time at the right time
            if (currentNewsgroupId != -1 && currentMessageDate != -1) {
              Log.d(
                  TAG,
                  "-----> Sync interrupted! Last sync date in group "
                      + currentNewsgroupId
                      + " is "
                      + currentMessageDate);
              NewsgroupQueries newsgroupQueries = new NewsgroupQueries(context);
              newsgroupQueries.setLastSyncDate(currentNewsgroupId, currentMessageDate);
              currentMessageDate = -1;
              currentNewsgroupId = -1;
            }
          }
          c.moveToNext();
        }
        Log.d(TAG, "************ FINISHED SYNC: " + syncNumber + "*********************");
      }
      c.close();
    }
  }
  @Override
  public boolean authenticate() {
    if (!super.authenticate()) {
      LOG.error(
          String.format(
              "blank username or password detected, no %s xword will be downloaded",
              this.getType()));
      return false;
    }

    final HttpUriRequest loginGet = RequestBuilder.get().setUri(NYT_LOGIN_URL).build();

    final String loginPage;
    try (final CloseableHttpResponse getResponse = this.getHttpClient().execute(loginGet)) {
      loginPage = EntityUtils.toString(getResponse.getEntity());
    } catch (final IOException e) {
      LOG.error("error while navigating to NYT login page", e);
      return false;
    }

    final String token;
    final String expires;

    try {
      final TagNode node = this.getCleaner().clean(loginPage);

      final Object[] foundNodes = node.evaluateXPath("//input[@name='token']");
      if (foundNodes.length != 1) {
        this.throwLoginException(
            "unexpected login page, found %d hidden token input elements, expected 1",
            foundNodes.length);
      }
      final TagNode hiddenTokenInput = (TagNode) foundNodes[0];
      token = hiddenTokenInput.getAttributeByName("value");
      LOG.debug("found hidden input token {}", token);

      final Object[] foundExpiresNodes = node.evaluateXPath("//input[@name='expires']");
      if (foundExpiresNodes.length != 1) {
        this.throwLoginException(
            "unexpected login page, found %d hidden token expiration input elements, expected 1",
            foundNodes.length);
      }
      final TagNode hiddenTokenExpiresInput = (TagNode) foundExpiresNodes[0];
      expires = hiddenTokenExpiresInput.getAttributeByName("value");
      LOG.debug("found hidden input token expiration {}", expires);
    } catch (LoginException | XPatherException e) {
      LOG.error("error while pulling login tokens from NYT login page", e);
      return false;
    }

    // @formatter:off
    final HttpUriRequest loginPost =
        RequestBuilder.post()
            .setUri("https://myaccount.nytimes.com/auth/login")
            .addParameter("is_continue", Boolean.FALSE.toString())
            .addParameter("token", token)
            .addParameter("expires", expires)
            .addParameter("userid", this.getLoginInfo().getUsername())
            .addParameter("password", this.getLoginInfo().getPassword())
            .addParameter("remember", Boolean.TRUE.toString())
            .build();
    // @formatter:on

    try (CloseableHttpResponse postResponse = this.getHttpClient().execute(loginPost)) {

      // successful NYT login should give 302 status
      final int responseStatus = postResponse.getStatusLine().getStatusCode();
      if (responseStatus != 302) {
        final String errorMessage =
            String.format("did not detect expected 302 redirect, got %d instead", responseStatus);
        throw new LoginException(errorMessage);
      }

      // successful NYT login redirects to the NYT homepage
      final Header location = postResponse.getFirstHeader("Location");
      // have seen this redirect both with and without the final portion
      final Pattern expectedRedirectLocation =
          Pattern.compile("http://www.nytimes.com(\\?login=email)*");
      final String actualRedirectLocation = location.getValue();
      final Matcher matcher = expectedRedirectLocation.matcher(actualRedirectLocation);
      if (!matcher.matches()) {
        final String errorMessage =
            String.format(
                "redirect to unexpected URL, expected %s, found Location=%s instead",
                expectedRedirectLocation, actualRedirectLocation);
        throw new LoginException(errorMessage);
      }

      // successful NYT login should set a few cookies
      final Header[] cookies = postResponse.getHeaders("Set-Cookie");
      if (cookies.length < 1) {
        throw new LoginException("no post login cookies set, login likely failed");
      }

    } catch (final IOException | LoginException e) {
      LOG.error("error while logging in, e={}", e.getMessage());
      return false;
    }

    LOG.info("successfully logged in to nyt");
    return true;
  }