/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.sync.jpake.stage;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.jpake.IncorrectZkpException;
import org.mozilla.gecko.sync.jpake.JPakeClient;
import org.mozilla.gecko.sync.jpake.JPakeCrypto;
import org.mozilla.gecko.sync.jpake.Zkp;
import org.mozilla.gecko.sync.setup.Constants;

public class ComputeFinalStage extends JPakeStage {

  @Override
  public void execute(JPakeClient jClient) {
    Logger.debug(LOG_TAG, "Computing final round.");

    // Check incoming message type.
    if (!jClient.jIncoming.get(Constants.JSON_KEY_TYPE).equals(jClient.theirSignerId + "2")) {
      Logger.error(LOG_TAG, "Invalid round 2 message: " + jClient.jIncoming.toJSONString());
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    }

    // Check incoming message fields.
    ExtendedJSONObject iPayload;
    ExtendedJSONObject zkpPayload;
    try {
      iPayload = jClient.jIncoming.getObject(Constants.JSON_KEY_PAYLOAD);
      if (iPayload == null
          || iPayload.getObject(Constants.ZKP_KEY_ZKP_A) == null) {
        Logger.error(LOG_TAG,
            "Invalid round 2 message: " + jClient.jIncoming.toJSONString());
        jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
        return;
      }
      zkpPayload = iPayload.getObject(Constants.ZKP_KEY_ZKP_A);
    } catch (NonObjectJSONException e) {
      Logger.error(LOG_TAG, "JSON object Exception.", e);
      jClient.abort(Constants.JPAKE_ERROR_INVALID);
      return;
    }

    if (!jClient.theirSignerId.equals(zkpPayload.get(Constants.ZKP_KEY_ID))) {
      Logger.error(LOG_TAG, "Invalid round 2 message: " + jClient.jIncoming.toJSONString());
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    }

    // Extract fields.
    jClient.jParty.otherA = new BigInteger((String) iPayload.get(Constants.ZKP_KEY_A), 16);

    // Extract ZKP.
    String gr = (String) zkpPayload.get(Constants.ZKP_KEY_GR);
    String b  = (String) zkpPayload.get(Constants.ZKP_KEY_B);
    String id = (String) zkpPayload.get(Constants.ZKP_KEY_ID);

    jClient.jParty.otherZkpA = new Zkp(new BigInteger(gr, 16), new BigInteger(b, 16), id);

    jClient.myKeyBundle = null;
    try {
      jClient.myKeyBundle = JPakeCrypto.finalRound(JPakeClient.secretAsBigInteger(jClient.secret), jClient.jParty);
    } catch (IncorrectZkpException e) {
      Logger.error(LOG_TAG, "ZKP mismatch");
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    } catch (NoSuchAlgorithmException e) {
      Logger.error(LOG_TAG, "NoSuchAlgorithmException", e);
      jClient.abort(Constants.JPAKE_ERROR_INTERNAL);
      return;
    } catch (InvalidKeyException e) {
      Logger.error(LOG_TAG, "InvalidKeyException", e);
      jClient.abort(Constants.JPAKE_ERROR_INTERNAL);
      return;
    } catch (UnsupportedEncodingException e) {
      Logger.error(LOG_TAG, "UnsupportedEncodingException", e);
      jClient.abort(Constants.JPAKE_ERROR_INTERNAL);
      return;
    }

    // Run next stage.
    jClient.runNextStage();
  }

}
