// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Boolberry developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#pragma once
#include "profile_tools.h"
#include "serialization/keyvalue_serialization.h"
#include "storages/portable_storage_template_helper.h"
#include "currency_basic.h"
#include "offers_service_basics.h"
#include "currency_protocol/blobdatatype.h"
#include "zlib_helper.h"
#include "bc_offers_service_basic.h"
#include "bc_attachments_helpers.h"

namespace bc_services
{


  struct offer_id
  {
    crypto::hash tx_id;
    uint64_t index;

    BEGIN_KV_SERIALIZE_MAP()
      KV_SERIALIZE_POD_AS_HEX_STRING(tx_id)
      KV_SERIALIZE(index)
    END_KV_SERIALIZE_MAP()
  };

#define ORDER_BY_TIMESTAMP                 0
#define ORDER_BY_AMOUNT_PRIMARY            1
#define ORDER_BY_AMOUNT_TARGET             2
#define ORDER_BY_AMOUNT_RATE               3
#define ORDER_BY_PAYMENT_TYPES             4
#define ORDER_BY_CONTACTS                  5
#define ORDER_BY_LOCATION                  6
#define ORDER_BY_NAME                      7

#define ORDER_CALLBACKS_COUNT               8


  typedef std::wstring(*to_low_case_ptr_type)(const std::wstring& soruce);

  void set_external_to_low_converter(to_low_case_ptr_type cb);

  typedef bool(*sort_offers_func_type)(const offer_details_ex* a, const offer_details_ex* b);
  extern std::vector<sort_offers_func_type> gsort_offers_predicates;
  bool is_offer_matched_by_filter(const offer_details_ex& o, const core_offers_filter& of, uint64_t currnet_time);
  bool filter_offers_list(std::list<offer_details_ex>& offers, const core_offers_filter& filter, uint64_t current_core_time);
  crypto::hash offer_id_from_hash_and_index(const crypto::hash& tx_id, uint64_t index);
  crypto::hash offer_id_from_hash_and_index(const offer_id& oid);
  const crypto::public_key get_offer_secure_key_by_index_from_tx(const currency::transaction& tx, size_t index);

  inline double calculate_offer_rate(const offer_details& od)
  {
    return od.amount_primary > 0 ? static_cast<double>(od.amount_target) / od.amount_primary : 0;
  }

  template<class offer_instruction_t>
  void put_offer_into_attachment(const offer_instruction_t& oi, std::vector<currency::attachment_v>& att_container)
  {
    std::string json_buff;
    currency::tx_service_attachment att = AUTO_VAL_INIT(att);

    bool r = bc_services::pack_attachment_as_gzipped_json(oi, att);
    CHECK_AND_ASSERT_MES(r, void(), "Failed to pack_attachment_as_gzipped_json");

    att.service_id = BC_OFFERS_SERVICE_ID;
    if (typeid(offer_instruction_t) == typeid(offer_details))
    {
      att.instruction = BC_OFFERS_SERVICE_INSTRUCTION_ADD;
    }
    else if (typeid(offer_instruction_t) == typeid(update_offer))
    {
      att.instruction = BC_OFFERS_SERVICE_INSTRUCTION_UPD;
    }
    else if (typeid(offer_instruction_t) == typeid(cancel_offer))
    {
      att.instruction = BC_OFFERS_SERVICE_INSTRUCTION_DEL;
    }
    else
    {
      LOG_ERROR("Filed to pack tx_service_attachment in bc_offers_service, unknown_type: " << typeid(offer_instruction_t).name());
      return;
    }
    
    //if att.security vector contain public_key then transaction constructor put there correct public_key related with one time transaction keys
    if (att.instruction == BC_OFFERS_SERVICE_INSTRUCTION_ADD || att.instruction == BC_OFFERS_SERVICE_INSTRUCTION_UPD)
      att.security.push_back(currency::null_pkey);

    att_container.push_back(att);
  }

  inline currency::blobdata make_offer_sig_blob(const update_offer& uo)
  {
    currency::blobdata bd;
    epee::string_tools::apped_pod_to_strbuff(bd, uo.tx_id);
    epee::string_tools::apped_pod_to_strbuff(bd, uo.offer_index);
    bd += epee::serialization::store_t_to_binary(uo.of);
    return bd;
  }

  inline currency::blobdata make_offer_sig_blob(const cancel_offer& co)
  {
    currency::blobdata bd;
    epee::string_tools::apped_pod_to_strbuff(bd, co.tx_id);
    epee::string_tools::apped_pod_to_strbuff(bd, co.offer_index);
    return bd;
  }

  //----------------------------------------------------------------------------------------------------
  template<class t_contained_type, class t_srv_attachments_container>
  bool extract_type_and_add(const std::string& body, t_srv_attachments_container& srv_cnt)
  {
    std::string json_buff;
    if (!epee::zlib_helper::unpack(body, json_buff))
    {
      LOG_ERROR("Filed to unpack tx_service_attachment in bc_offers_service, tx_id");
      return false;
    }
    t_contained_type obj = AUTO_VAL_INIT(obj);
    bool r = epee::serialization::load_t_from_json(obj, json_buff);
    if (!r)
    {
      LOG_ERROR("Filed to load json from tx_service_attachment in bc_offers_service, body: " << body);
      return false;
    }
    srv_cnt.push_back(obj);
    return true;
  }
  //----------------------------------------------------------------------------------------------------
  template<class t_srv_attachments_container, class t_tx_attachments_container>
  void extract_market_instructions(t_srv_attachments_container& cnt, const t_tx_attachments_container& raw_att)
  {
    PROFILE_FUNC("bc_service::extract_market_instructions");
    for (auto& att : raw_att)
    {
      if (att.type() == typeid(currency::tx_service_attachment))
      {
        const currency::tx_service_attachment& sa = boost::get<const currency::tx_service_attachment>(att);
        if (sa.service_id == BC_OFFERS_SERVICE_ID)
        {
          if (sa.instruction == BC_OFFERS_SERVICE_INSTRUCTION_ADD)
          {
             extract_type_and_add<bc_services::offer_details>(sa.body, cnt);
          }
          else if (sa.instruction == BC_OFFERS_SERVICE_INSTRUCTION_UPD)
          {
             extract_type_and_add<bc_services::update_offer>(sa.body, cnt);
          }
          else if (sa.instruction == BC_OFFERS_SERVICE_INSTRUCTION_DEL)
          {
            extract_type_and_add<bc_services::cancel_offer>(sa.body, cnt);
          }
        }
      }
    }
  }




#define OFFER_TYPE_PRIMARY_TO_TARGET        0
#define OFFER_TYPE_TARGET_TO_PRIMARY        1
#define OFFER_TYPE_GOODS_TO_PRIMARY         2
#define OFFER_TYPE_PRIMARY_TO_GOODS         3

#define OFFER_TYPE_MASK_PRIMARY_TO_TARGET   0x00000001
#define OFFER_TYPE_MASK_TARGET_TO_PRIMARY   0x00000002
#define OFFER_TYPE_MASK_GOODS_TO_PRIMARY    0x00000004
#define OFFER_TYPE_MASK_PRIMARY_TO_GOODS    0x00000008

#define OFFER_TYPE_UNDEFINED                255


  struct offer_details_ex_with_hash : public offer_details_ex
  {
    crypto::hash h;
    mutable std::string nxt_offer; //in case of updated offer
  };

  typedef offer_details_ex_with_hash odeh;

  inline crypto::hash extract_id(const odeh& v)             { return v.h; }
  inline uint64_t     extrct_timestamp(const odeh& v)       { return v.timestamp; }
  inline uint64_t     extract_amount_primary(const odeh& v) { return v.amount_primary; }
  inline uint64_t     extract_amount_target(const odeh& v)  { return v.amount_target; }
  inline double       extract_rate(const odeh& v)           { return calculate_offer_rate(v); }
  inline size_t       extract_payment_types(const odeh& v)  { return v.payment_types.size(); }
  inline std::string  extract_contacts(const odeh& v)       { return v.contacts; }
  inline std::string  extract_location(const odeh& v) {

#ifndef ANDROID_BUILD
    return currency::utf8_to_lower(v.location_country + v.location_city);
#else 
    return "UNSUPORTED";
#endif
  }
  inline std::string  extract_name(const odeh& v)           { 
#ifndef ANDROID_BUILD
    return currency::utf8_to_lower(v.target); 
#else
    return "UNSUPORTED";
#endif
  }

  template<int sort_type>
  struct sort_id_to_type
  {};

#define TAG_NAME(tag_name) struct tag_name{};
  TAG_NAME(by_id);
  TAG_NAME(by_timestamp);
  TAG_NAME(by_amount_primary);
  TAG_NAME(by_amount_target);
  TAG_NAME(by_rate);
  TAG_NAME(by_payment_types);
  TAG_NAME(by_contacts);
  TAG_NAME(by_location);
  TAG_NAME(by_name);
#undef TAG_NAME

  template<>  struct sort_id_to_type<ORDER_BY_TIMESTAMP>      { typedef by_timestamp index_type; };
  template<>  struct sort_id_to_type<ORDER_BY_AMOUNT_PRIMARY> { typedef by_amount_primary index_type; };
  template<>  struct sort_id_to_type<ORDER_BY_AMOUNT_TARGET>  { typedef by_amount_target index_type; };
  template<>  struct sort_id_to_type<ORDER_BY_AMOUNT_RATE>    { typedef by_rate index_type; };
  template<>  struct sort_id_to_type<ORDER_BY_PAYMENT_TYPES>  { typedef by_payment_types index_type; };
  template<>  struct sort_id_to_type<ORDER_BY_CONTACTS>       { typedef by_contacts index_type; };
  template<>  struct sort_id_to_type<ORDER_BY_LOCATION>       { typedef by_location index_type; };
  template<>  struct sort_id_to_type<ORDER_BY_NAME>           { typedef by_name index_type; };
}
