/*
 * This file is part of the Trezor project, https://trezor.io/
 *
 * Copyright (C) 2017 Saleem Rashid <trezor@saleemrashid.com>
 *
 * 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 3 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/>.
 */

void fsm_msgNEMGetAddress(NEMGetAddress *msg) {
  if (!msg->has_network) {
    msg->network = NEM_NETWORK_MAINNET;
  }

  const char *network;
  CHECK_PARAM((network = nem_network_name(msg->network)),
              _("Invalid NEM network"));

  CHECK_INITIALIZED
  CHECK_PIN

  RESP_INIT(NEMAddress);

  HDNode *node = fsm_getDerivedNode(ED25519_KECCAK_NAME, msg->address_n,
                                    msg->address_n_count, NULL);
  if (!node) return;

  if (!hdnode_get_nem_address(node, msg->network, resp->address)) return;

  if (msg->has_show_display && msg->show_display) {
    char desc[16];
    strlcpy(desc, network, sizeof(desc));
    strlcat(desc, ":", sizeof(desc));

    if (!fsm_layoutAddress(resp->address, desc, true, 0, msg->address_n,
                           msg->address_n_count, false, NULL, 0, NULL)) {
      return;
    }
  }

  msg_write(MessageType_MessageType_NEMAddress, resp);
  layoutHome();
}

void fsm_msgNEMSignTx(NEMSignTx *msg) {
  const char *reason;

#define NEM_CHECK_PARAM(s) CHECK_PARAM((reason = (s)) == NULL, reason)
#define NEM_CHECK_PARAM_WHEN(b, s) \
  CHECK_PARAM(!(b) || (reason = (s)) == NULL, reason)

  CHECK_PARAM(msg->has_transaction, _("No common provided"));

  // Ensure exactly one transaction is provided
  unsigned int provided = msg->has_transfer + msg->has_provision_namespace +
                          msg->has_mosaic_creation + msg->has_supply_change +
                          msg->has_aggregate_modification +
                          msg->has_importance_transfer;
  CHECK_PARAM(provided != 0, _("No transaction provided"));
  CHECK_PARAM(provided == 1, _("More than one transaction provided"));

  NEM_CHECK_PARAM(nem_validate_common(&msg->transaction, false));
  NEM_CHECK_PARAM_WHEN(
      msg->has_transfer,
      nem_validate_transfer(&msg->transfer, msg->transaction.network));
  NEM_CHECK_PARAM_WHEN(
      msg->has_provision_namespace,
      nem_validate_provision_namespace(&msg->provision_namespace,
                                       msg->transaction.network));
  NEM_CHECK_PARAM_WHEN(msg->has_mosaic_creation,
                       nem_validate_mosaic_creation(&msg->mosaic_creation,
                                                    msg->transaction.network));
  NEM_CHECK_PARAM_WHEN(msg->has_supply_change,
                       nem_validate_supply_change(&msg->supply_change));
  NEM_CHECK_PARAM_WHEN(msg->has_aggregate_modification,
                       nem_validate_aggregate_modification(
                           &msg->aggregate_modification, !msg->has_multisig));
  NEM_CHECK_PARAM_WHEN(
      msg->has_importance_transfer,
      nem_validate_importance_transfer(&msg->importance_transfer));

  bool cosigning = msg->has_cosigning && msg->cosigning;
  if (msg->has_multisig) {
    NEM_CHECK_PARAM(nem_validate_common(&msg->multisig, true));

    CHECK_PARAM(msg->transaction.network == msg->multisig.network,
                _("Inner transaction network is different"));
  } else {
    CHECK_PARAM(!cosigning, _("No multisig transaction to cosign"));
  }

  CHECK_INITIALIZED
  CHECK_PIN

  const char *network = nem_network_name(msg->transaction.network);

  if (msg->has_multisig) {
    char address[NEM_ADDRESS_SIZE + 1];
    nem_get_address(msg->multisig.signer.bytes, msg->multisig.network, address);

    if (!nem_askMultisig(address, network, cosigning, msg->transaction.fee)) {
      fsm_sendFailure(FailureType_Failure_ActionCancelled,
                      _("Signing cancelled by user"));
      layoutHome();
      return;
    }
  }

  RESP_INIT(NEMSignedTx);

  HDNode *node =
      fsm_getDerivedNode(ED25519_KECCAK_NAME, msg->transaction.address_n,
                         msg->transaction.address_n_count, NULL);
  if (!node) return;

  hdnode_fill_public_key(node);

  const NEMTransactionCommon *common =
      msg->has_multisig ? &msg->multisig : &msg->transaction;

  char address[NEM_ADDRESS_SIZE + 1];
  hdnode_get_nem_address(node, common->network, address);

  if (msg->has_transfer) {
    nem_canonicalizeMosaics(&msg->transfer);
  }

  if (msg->has_transfer && !nem_askTransfer(common, &msg->transfer, network)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled,
                    _("Signing cancelled by user"));
    layoutHome();
    return;
  }

  if (msg->has_provision_namespace &&
      !nem_askProvisionNamespace(common, &msg->provision_namespace, network)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled,
                    _("Signing cancelled by user"));
    layoutHome();
    return;
  }

  if (msg->has_mosaic_creation &&
      !nem_askMosaicCreation(common, &msg->mosaic_creation, network, address)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled,
                    _("Signing cancelled by user"));
    layoutHome();
    return;
  }

  if (msg->has_supply_change &&
      !nem_askSupplyChange(common, &msg->supply_change, network)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled,
                    _("Signing cancelled by user"));
    layoutHome();
    return;
  }

  if (msg->has_aggregate_modification &&
      !nem_askAggregateModification(common, &msg->aggregate_modification,
                                    network, !msg->has_multisig)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled,
                    _("Signing cancelled by user"));
    layoutHome();
    return;
  }

  if (msg->has_importance_transfer &&
      !nem_askImportanceTransfer(common, &msg->importance_transfer, network)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled,
                    _("Signing cancelled by user"));
    layoutHome();
    return;
  }

  nem_transaction_ctx context;
  nem_transaction_start(&context, &node->public_key[1], resp->data.bytes,
                        sizeof(resp->data.bytes));

  if (msg->has_multisig) {
    uint8_t buffer[sizeof(resp->data.bytes)];

    nem_transaction_ctx inner;
    nem_transaction_start(&inner, msg->multisig.signer.bytes, buffer,
                          sizeof(buffer));

    if (msg->has_transfer &&
        !nem_fsmTransfer(&inner, NULL, &msg->multisig, &msg->transfer)) {
      layoutHome();
      return;
    }

    if (msg->has_provision_namespace &&
        !nem_fsmProvisionNamespace(&inner, &msg->multisig,
                                   &msg->provision_namespace)) {
      layoutHome();
      return;
    }

    if (msg->has_mosaic_creation &&
        !nem_fsmMosaicCreation(&inner, &msg->multisig, &msg->mosaic_creation)) {
      layoutHome();
      return;
    }

    if (msg->has_supply_change &&
        !nem_fsmSupplyChange(&inner, &msg->multisig, &msg->supply_change)) {
      layoutHome();
      return;
    }

    if (msg->has_aggregate_modification &&
        !nem_fsmAggregateModification(&inner, &msg->multisig,
                                      &msg->aggregate_modification)) {
      layoutHome();
      return;
    }

    if (msg->has_importance_transfer &&
        !nem_fsmImportanceTransfer(&inner, &msg->multisig,
                                   &msg->importance_transfer)) {
      layoutHome();
      return;
    }

    if (!nem_fsmMultisig(&context, &msg->transaction, &inner, cosigning)) {
      layoutHome();
      return;
    }
  } else {
    if (msg->has_transfer &&
        !nem_fsmTransfer(&context, node, &msg->transaction, &msg->transfer)) {
      layoutHome();
      return;
    }

    if (msg->has_provision_namespace &&
        !nem_fsmProvisionNamespace(&context, &msg->transaction,
                                   &msg->provision_namespace)) {
      layoutHome();
      return;
    }

    if (msg->has_mosaic_creation &&
        !nem_fsmMosaicCreation(&context, &msg->transaction,
                               &msg->mosaic_creation)) {
      layoutHome();
      return;
    }

    if (msg->has_supply_change &&
        !nem_fsmSupplyChange(&context, &msg->transaction,
                             &msg->supply_change)) {
      layoutHome();
      return;
    }

    if (msg->has_aggregate_modification &&
        !nem_fsmAggregateModification(&context, &msg->transaction,
                                      &msg->aggregate_modification)) {
      layoutHome();
      return;
    }

    if (msg->has_importance_transfer &&
        !nem_fsmImportanceTransfer(&context, &msg->transaction,
                                   &msg->importance_transfer)) {
      layoutHome();
      return;
    }
  }

  resp->has_data = true;
  resp->data.size =
      nem_transaction_end(&context, node->private_key, resp->signature.bytes);

  resp->has_signature = true;
  resp->signature.size = sizeof(ed25519_signature);

  msg_write(MessageType_MessageType_NEMSignedTx, resp);
  layoutHome();
}

void fsm_msgNEMDecryptMessage(NEMDecryptMessage *msg) {
  RESP_INIT(NEMDecryptedMessage);

  CHECK_INITIALIZED

  CHECK_PARAM(nem_network_name(msg->network), _("Invalid NEM network"));
  CHECK_PARAM(msg->has_payload, _("No payload provided"));
  CHECK_PARAM(msg->payload.size >= NEM_ENCRYPTED_PAYLOAD_SIZE(0),
              _("Invalid encrypted payload"));
  CHECK_PARAM(msg->has_public_key, _("No public key provided"));
  CHECK_PARAM(msg->public_key.size == 32, _("Invalid public key"));

  char address[NEM_ADDRESS_SIZE + 1];
  nem_get_address(msg->public_key.bytes, msg->network, address);

  layoutNEMDialog(&bmp_icon_question, _("Cancel"), _("Confirm"),
                  _("Decrypt message"), _("Confirm address?"), address);
  if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
    layoutHome();
    return;
  }

  CHECK_PIN

  const HDNode *node = fsm_getDerivedNode(ED25519_KECCAK_NAME, msg->address_n,
                                          msg->address_n_count, NULL);
  if (!node) return;

  const uint8_t *salt = msg->payload.bytes;
  uint8_t *iv = &msg->payload.bytes[NEM_SALT_SIZE];

  const uint8_t *payload = &msg->payload.bytes[NEM_SALT_SIZE + AES_BLOCK_SIZE];
  size_t size = msg->payload.size - NEM_SALT_SIZE - AES_BLOCK_SIZE;

  // hdnode_nem_decrypt mutates the IV, so this will modify msg
  bool ret = hdnode_nem_decrypt(node, msg->public_key.bytes, iv, salt, payload,
                                size, resp->payload.bytes);
  if (!ret) {
    fsm_sendFailure(FailureType_Failure_ProcessError,
                    _("Failed to decrypt payload"));
    layoutHome();
    return;
  }

  resp->has_payload = true;
  resp->payload.size = NEM_DECRYPTED_SIZE(resp->payload.bytes, size);

  layoutNEMTransferPayload(resp->payload.bytes, resp->payload.size, true);
  if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
    layoutHome();
    return;
  }

  msg_write(MessageType_MessageType_NEMDecryptedMessage, resp);
  layoutHome();
}