#!/usr/bin/env python3
import random
import sys

from jmbase import get_log, jmprint, EXIT_ARGERROR
from jmbitcoin import amount_to_str
from jmclient import YieldGeneratorBasic, ygmain, jm_single

# This is a maker for the purposes of generating a yield from held bitcoins
# while maximising the difficulty of spying on blockchain activity.
# This is primarily attempted by randomizing all aspects of orders
# after transactions wherever possible.

# YIELD GENERATOR SETTINGS ARE NOW IN YOUR joinmarket.cfg CONFIG FILE
# (You can also use command line flags; see --help for this script).

jlog = get_log()

class YieldGeneratorPrivacyEnhanced(YieldGeneratorBasic):

    def __init__(self, wallet_service, offerconfig):
        super().__init__(wallet_service, offerconfig)

    def select_input_mixdepth(self, available, offer, amount):
        """Mixdepths are in cyclic order and we select the mixdepth to
        maximize the largest interval of non-available mixdepths by choosing
        the first mixdepth available after the largest such interval.
        This forces the biggest UTXOs to stay in a bulk of few mixdepths so
        that the maker can always maximize the size of his orders even when
        some coins are sent from the last to the first mixdepth"""
        # We sort the available depths for linear scaling of the interval search
        available = sorted(available.keys())
        # For an available mixdepth, the smallest interval starting from this mixdepth
        # containing all the other available mixdepths necessarily ends at the previous
        # available mixdepth in the cyclic order. The successive difference of sorted
        # depths is then the length of the largest interval ending at the same mixdepth
        # without any available mixdepths, modulo the number of mixdepths if 0 is in it
        # which is only the case for the first (in linear order) available mixdepth case
        intervals = ([self.wallet_service.mixdepth + 1 + available[0] - available[-1]] + \
                    [(available[i+1] - available[i]) for i in range(len(available)-1)])
        # We return the mixdepth value at which the largest interval without
        # available mixdepths ends. Selecting this mixdepth will send the CoinJoin
        # outputs closer to the others available mixdepths which are after in cyclical order
        return available[max(range(len(available)), key = intervals.__getitem__)]

    def create_my_orders(self):
        mix_balance = self.get_available_mixdepths()
        # We publish ONLY the maximum amount and use minsize for lower bound;
        # leave it to oid_to_order to figure out the right depth to use.
        f = '0'
        if self.ordertype in ['swreloffer', 'sw0reloffer']:
            f = self.cjfee_r
        elif self.ordertype in ['swabsoffer', 'sw0absoffer']:
            f = str(self.txfee_contribution + self.cjfee_a)
        mix_balance = dict([(m, b) for m, b in mix_balance.items() if b > self.minsize])
        if len(mix_balance) == 0:
            jlog.error('You do not have the minimum required amount of coins'
                       ' to be a maker: ' + str(self.minsize) + \
                       '\nTry setting txfee_contribution to zero and/or '
                       'lowering the minsize.')
            return []
        max_mix = max(mix_balance, key=mix_balance.get)

        # randomizing the different values
        randomize_txfee = int(random.uniform(
            self.txfee_contribution * (1 - float(self.txfee_contribution_factor)),
            self.txfee_contribution * (1 + float(self.txfee_contribution_factor))))
        randomize_minsize = int(random.uniform(
            self.minsize * (1 - float(self.size_factor)),
            self.minsize * (1 + float(self.size_factor))))
        if randomize_minsize < jm_single().DUST_THRESHOLD:
            jlog.warn("Minsize was randomized to below dust; resetting to dust "
                      "threshold: " + amount_to_str(jm_single().DUST_THRESHOLD))
            randomize_minsize = jm_single().DUST_THRESHOLD
        possible_maxsize = mix_balance[max_mix] - max(jm_single().DUST_THRESHOLD, randomize_txfee)
        randomize_maxsize = int(random.uniform(possible_maxsize * (1 - float(self.size_factor)),
                                               possible_maxsize))

        if self.ordertype in ['swabsoffer', 'sw0absoffer']:
            randomize_cjfee = int(random.uniform(float(self.cjfee_a) * (1 - float(self.cjfee_factor)),
                                                 float(self.cjfee_a) * (1 + float(self.cjfee_factor))))
            randomize_cjfee = randomize_cjfee + randomize_txfee
        else:
            randomize_cjfee = random.uniform(float(f) * (1 - float(self.cjfee_factor)),
                                             float(f) * (1 + float(self.cjfee_factor)))
            randomize_cjfee = "{0:.6f}".format(randomize_cjfee)  # round to 6 decimals

        order = {'oid': 0,
                 'ordertype': self.ordertype,
                 'minsize': randomize_minsize,
                 'maxsize': randomize_maxsize,
                 'txfee': randomize_txfee,
                 'cjfee': str(randomize_cjfee)}

        # sanity check
        assert order['minsize'] >= jm_single().DUST_THRESHOLD
        assert order['minsize'] <= order['maxsize']
        if order['ordertype'] in ['swreloffer', 'sw0reloffer']:
            for i in range(20):
                if order['txfee'] < (float(order['cjfee']) * order['minsize']):
                    break
                order['txfee'] = int(order['txfee'] / 2)
                jlog.info('Warning: too high txfee to be profitable, halving it to: ' + str(order['txfee']))
            else:
                jlog.error("Tx fee reduction algorithm failed. Quitting.")
                sys.exit(EXIT_ARGERROR)
        return [order]


if __name__ == "__main__":
    ygmain(YieldGeneratorPrivacyEnhanced, nickserv_password='')
    jmprint('done', "success")
