/*
  This file is part of TALER
  (C) 2014 GNUnet e.V.
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.
  TALER 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 Affero General Public License for more details.
  You should have received a copy of the GNU Affero General Public License along with
  TALER; see the file COPYING.  If not, If not, see 
*/
/**
 * @file taler-mint-httpd_deposit.c
 * @brief Handle /deposit requests
 * @author Florian Dold
 * @author Benedikt Mueller
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include 
#include 
#include 
#include 
#include "mint.h"
#include "mint_db.h"
#include "taler_types.h"
#include "taler_signatures.h"
#include "taler_rsa.h"
#include "taler_json_lib.h"
#include "taler-mint-httpd_json.h"
#include "taler-mint-httpd_keys.h"
#include "taler-mint-httpd_deposit.h"
/**
 * Send confirmation of deposit success to client.
 *
 * @param connection connection to the client
 * @param deposit deposit request to confirm
 * @return MHD result code
 */
static int
helper_deposit_send_response_success (struct MHD_Connection *connection,
                                      struct Deposit *deposit)
{
  // FIXME: return more information here
  return request_send_json_pack (connection, MHD_HTTP_OK,
                                 "{s:s}", "status", "DEPOSIT_OK");
}
/**
 * Handle a "/deposit" request
 *
 * @param rh context of the handler
 * @param connection the MHD connection to handle
 * @param[IN|OUT] connection_cls the connection's closure (can be updated)
 * @param upload_data upload data
 * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
 * @return MHD result code
  */
int
TALER_MINT_handler_deposit (struct RequestHandler *rh,
                            struct MHD_Connection *connection,
                            void **connection_cls,
                            const char *upload_data,
                            size_t *upload_data_size)
{
  json_t *json;
  struct Deposit *deposit;
  json_t *wire;
  json_t *resp;
  char *wire_enc = NULL;
  const char *deposit_type;
  struct MintKeyState *key_state;
  struct TALER_CoinPublicInfo coin_info;
  struct TALER_RSA_Signature ubsig;
  size_t len;
  int resp_code;
  PGconn *db_conn;
  int res;
  res = process_post_json (connection,
                           connection_cls,
                           upload_data, upload_data_size,
                           &json);
  if (GNUNET_SYSERR == res)
  {
    // FIXME: return 'internal error'
    GNUNET_break (0);
    return MHD_NO;
  }
  if (GNUNET_NO == res)
    return MHD_YES;
  if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  deposit = NULL;
  wire = NULL;
  resp = NULL;
  if (-1 == json_unpack (json,
                         "{s:s s:o}",
                         "type", &deposit_type,
                         "wire", &wire))
  {
    GNUNET_break_op (0);
    resp = json_pack ("{s:s}", "error", "Bad format");
    resp_code = MHD_HTTP_BAD_REQUEST;
    goto EXITIF_exit;
  }
  if (NULL == (wire_enc = json_dumps (wire, JSON_COMPACT|JSON_SORT_KEYS)))
  {
    GNUNET_break_op (0);
    resp = json_pack ("{s:s}", "error", "Bad format");
    resp_code = MHD_HTTP_BAD_REQUEST;
    goto EXITIF_exit;
  }
  len = strlen (wire_enc) + 1;
  deposit = GNUNET_malloc (sizeof (struct Deposit) + len);
#define EXITIF(cond)                                              \
  do {                                                            \
    if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \
  } while (0)
#define PARSE_DATA(field, addr)                                         \
  EXITIF (GNUNET_OK != request_json_require_nav                         \
          (connection, json,                                            \
           JNAV_FIELD, field, JNAV_RET_DATA, addr, sizeof (*addr)))
  PARSE_DATA ("coin_pub", &deposit->coin_pub);
  PARSE_DATA ("denom_pub", &deposit->denom_pub);
  PARSE_DATA ("ubsig", &ubsig);
  PARSE_DATA ("merchant_pub", &deposit->merchant_pub);
  PARSE_DATA ("H_a", &deposit->h_contract);
  PARSE_DATA ("H_wire", &deposit->h_wire);
  PARSE_DATA ("csig", &deposit->coin_sig);
  PARSE_DATA ("transaction_id", &deposit->transaction_id);
#undef PARSE_DATA
  if (0 == strcmp ("DIRECT_DEPOSIT", deposit_type))
    deposit->purpose.purpose = htonl (TALER_SIGNATURE_DEPOSIT);
  else if (0 == strcmp ("INCREMENTAL_DEPOSIT", deposit_type))
    deposit->purpose.purpose = htonl (TALER_SIGNATURE_INCREMENTAL_DEPOSIT);
  else
  {
    GNUNET_break_op (0);
    resp = json_pack ("{s:s}", "error", "Bad format");
    resp_code = MHD_HTTP_BAD_REQUEST;
    goto EXITIF_exit;
  }
  deposit->purpose.size = htonl (sizeof (struct Deposit)
                                 - offsetof (struct Deposit, purpose));
  memcpy (&coin_info.coin_pub,
          &deposit->coin_pub,
          sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
  coin_info.denom_pub = deposit->denom_pub;
  coin_info.denom_sig = ubsig;
  key_state = TALER_MINT_key_state_acquire ();
  if (GNUNET_YES != TALER_MINT_test_coin_valid (key_state,
                                &coin_info))
  {
    TALER_MINT_key_state_release (key_state);
    resp = json_pack ("{s:s}", "error", "Coin is not valid");
    resp_code = MHD_HTTP_NOT_FOUND;
    goto EXITIF_exit;
  }
  TALER_MINT_key_state_release (key_state);
  /*
  if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_DEPOSIT,
                                                      &deposit->purpose,
                                                      &deposit->coin_sig,
                                                      &deposit->coin_pub))
  {
    resp = json_pack ("{s:s}", "error", "Signature verfication failed");
    resp_code = MHD_HTTP_NOT_FOUND;
    goto EXITIF_exit;
  }
  */
  /* Check if we already received the same deposit permission,
   * or the coin was already deposited */
  {
    struct Deposit *existing_deposit;
    int res;
    res = TALER_MINT_DB_get_deposit (db_conn,
                                     &deposit->coin_pub,
                                     &existing_deposit);
    if (GNUNET_YES == res)
    {
      // FIXME: memory leak
      if (0 == memcmp (existing_deposit, deposit, sizeof (struct Deposit)))
        return helper_deposit_send_response_success (connection, deposit);
      // FIXME: in the future, check if there's enough credits
      // left on the coin. For now: refuse
      // FIXME: return more information here
      return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN,
                                     "{s:s}",
                                     "error", "double spending");
    }
    if (GNUNET_SYSERR == res)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
  }
  {
    struct KnownCoin known_coin;
    int res;
    res = TALER_MINT_DB_get_known_coin (db_conn, &coin_info.coin_pub, &known_coin);
    if (GNUNET_YES == res)
    {
      // coin must have been refreshed
      // FIXME: check
      // FIXME: return more information here
      return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN,
                                     "{s:s}",
                                     "error", "coin was refreshed");
    }
    if (GNUNET_SYSERR == res)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    /* coin valid but not known => insert into DB */
    known_coin.is_refreshed = GNUNET_NO;
    known_coin.expended_balance = TALER_amount_ntoh (deposit->amount);
    known_coin.public_info = coin_info;
    if (GNUNET_OK != TALER_MINT_DB_insert_known_coin (db_conn, &known_coin))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
  }
  if (GNUNET_OK != TALER_MINT_DB_insert_deposit (db_conn, deposit))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return helper_deposit_send_response_success (connection, deposit);
 EXITIF_exit:
  if (NULL != resp)
    res = send_response_json (connection, resp, resp_code);
  else
    res = MHD_NO;
  if (NULL != wire)
    json_decref (wire);
  if (NULL != deposit)
    GNUNET_free (deposit);
  if (NULL != wire_enc)
    GNUNET_free (wire_enc);
  return res;
#undef EXITIF
#undef PARSE_DATA
}
/* end of taler-mint-httpd_deposit.c */