from __future__ import print_function

import io
import logging
import os
import binascii
import sys

from ConfigParser import SafeConfigParser, NoOptionError

import jmbitcoin as btc

from jmclient import (get_p2pk_vbyte, get_p2sh_vbyte, JsonRpc, set_config,
                      get_network)

logFormatter = logging.Formatter(
    "%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s")
log = logging.getLogger('CoinSwapCS')
log.setLevel(logging.DEBUG)

debug_silence = [False]
import jmbase.support
jmbase.support.debug_silence = [True]
#consoleHandler = logging.StreamHandler(stream=sys.stdout)
class CoinSwapStreamHandler(logging.StreamHandler):

    def __init__(self, stream):
        super(CoinSwapStreamHandler, self).__init__(stream)

    def emit(self, record):
        if not debug_silence[0]:
            super(CoinSwapStreamHandler, self).emit(record)


consoleHandler = CoinSwapStreamHandler(stream=sys.stdout)
consoleHandler.setFormatter(logFormatter)

log.debug('CoinSwapCS logging started.')

class AttributeDict(object):
    """
    A class to convert a nested Dictionary into an object with key-values
    accessibly using attribute notation (AttributeDict.attribute) instead of
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttributeDict.attr.attr)
    """

    def __init__(self, **entries):
        self.currentlogpath = None
        self.add_entries(**entries)

    def add_entries(self, **entries):
        for key, value in entries.items():
            if type(value) is dict:
                self.__dict__[key] = AttributeDict(**value)
            else:
                self.__dict__[key] = value

    def __setattr__(self, name, value):
        if name == 'logs_path' and value != self.currentlogpath:
            self.currentlogpath = value
            logFormatter = logging.Formatter(
                ('%(asctime)s [%(threadName)-12.12s] '
                 '[%(levelname)-5.5s]  %(message)s'))
            fileHandler = logging.FileHandler(value + ".log")
            fileHandler.setFormatter(logFormatter)
            log.addHandler(fileHandler)

        super(AttributeDict, self).__setattr__(name, value)

    def __getitem__(self, key):
        """
        Provides dict-style access to attributes
        """
        return getattr(self, key)


global_singleton = AttributeDict()
global_singleton.CSCS_VERSION = 0.1
global_singleton.APPNAME = "CoinSwapCS"
global_singleton.homedir = None
global_singleton.BITCOIN_DUST_THRESHOLD = 2730
global_singleton.DUST_THRESHOLD = 10 * global_singleton.BITCOIN_DUST_THRESHOLD
global_singleton.bc_interface = None
global_singleton.logs_path = None
global_singleton.config = SafeConfigParser()
#This is reset to a full path after load_coinswap_config call
global_singleton.config_location = 'coinswapcs.cfg'
#Not currently exposed in config file but could be; it is not expected that
#confirmation for one block could conceivably take this long
global_singleton.one_confirm_timeout = 7200

def cs_single():
    return global_singleton

def get_log():
    return log

defaultconfig = \
    """
[BLOCKCHAIN]
#options: bitcoin-rpc, regtest, (no non-Bitcoin Core currently supported)
blockchain_source = bitcoin-rpc
network = mainnet
rpc_host = localhost
rpc_port = 8332
rpc_user = bitcoin
rpc_password = password

[TIMEOUT]
#How long to wait, by default, in seconds, before giving up on the counterparty
#and executing backout. This is only applied in cases where response is intended
#to be immediate.
default_network_timeout = 60
#How long to wait (in seconds, integer only) for the counterparty to confirm
#blockchain state that we've already seen (if client);
#this is to account for propagation delays on the BTC network,
#mainly. Used in waiting to proceed to second phase after first (TX0,TX1) is
#complete.
propagation_buffer = 120
#How many blocks to wait for ensured confirmation for the first stage (funding) txs.
#Note that the this value must be agreed with the server.
tx01_confirm_wait = 2
#
#***LOCKTIMES***
#
#These are critical to CoinSwap's design; probably better not to change them,
#but if you do, read the following notes and make sure you understand.
#Also note these variables are ONLY for client, server uses the variable in the
#SERVER section of the config (server/client_locktime_range).
#Locktime for TX3 (server's timeout); the server can refund her pay-in transaction
#after this number of blocks, from the starting time
lock_server = 50
#Locktime for TX2 (client's timeout); the client can refund her pay-in transaction
#after this number of blocks, from the starting time. Note that this has to be a
#longer timeout than that for the server (generally it should be ~2xserver timeout).
lock_client = 100

[SESSIONS]
#Location of directory where sessions are stored for recovery, it is located under
#the main coinswap data directory (APPDATA/.CoinSwapCS/). Note this contains
#keys and other privacy-sensitive information. Deleting its contents should be
#considered, but NEVER delete the contents until you are sure your previous
#coinswaps are completed. Also, NEVER EDIT THE CONTENTS OF SESSION FILES, only
#read them; editing could make a failed coinswap unrecoverable!
sessions_dir = sessions
[POLICY]
#Server should "blind" the amount of his side of the swap by adding an amount,
#here you can set the minimum acceptable to you, i.e. if you set 1000000, you
#require that the difference between your output and the server's output is at
#least 0.1BTC.
minimum_blinding_amount = 1000000
# for dust sweeping, try merge_algorithm = gradual
# for more rapid dust sweeping, try merge_algorithm = greedy
# for most rapid dust sweeping, try merge_algorithm = greediest
merge_algorithm = default
# the fee estimate is based on a projection of how many satoshis
# per kB are needed to get in one of the next N blocks, N set here
# as the value of 'tx_fees'. For CoinSwap, this is set to default
# 1 for highest priority processing; you can reduce it, but if it's
# a lot lower than the server's estimate, the server may refuse to
# transact.
tx_fees = 1
#A value, in satoshis/kB, above which the fee is not allowed to be.
#keep this fairly high, as exceeding it causes the program to 'panic'
#and shut down.
absurd_fee_per_kb = 250000
#The number of blocks to target to calculate the fee for backout transactions;
#these transactions are high priority since in certain cases they may become
#invalid after a certain amount of time (although only if the counterparty is
#malicious).
#Note that this and the following value must be agreed with the server.
backout_fee_target = 1
#Further to the above, an additional fee multiplier may be applied to give
#extra priority (by default target=1 block is considered enough, so x1.0 here).
backout_fee_multiplier = 1.0
# the range of confirmations passed to the `listunspent` bitcoind RPC call
# 1st value is the inclusive minimum, defaults to one confirmation
# 2nd value is the exclusive maximum, defaults to most-positive-bignum (Google Me!)
# leaving it unset or empty defers to bitcoind's default values, ie [1, 9999999]
#listunspent_args = []
# that's what you should do, unless you have a specific reason, eg:
#  spend from unconfirmed transactions:  listunspent_args = [0]
# display only unconfirmed transactions: listunspent_args = [0, 1]
# defend against small reorganizations:  listunspent_args = [3]
#   who is at risk of reorganization?:   listunspent_args = [0, 2]

[LOGGING]
# Set the log level for the output to the terminal/console
# Possible choices: DEBUG / INFO / WARNING / ERROR
# Log level for the files in the logs-folder will always be DEBUG
console_log_level = INFO

[SERVER]
#These settings can be safely ignored if you are running as client ('Alice').
#***
#source and destination chain is reserved for possible future implementations
#cross-chain.
source_chain = BTC
destination_chain = BTC
#Hidden service is the preferred way of serving; if use_onion is set to anything
#except 'false', clearnet modes will be ignored.
#(Tor will be started within the application)
use_onion = true
onion_port = 1234
#Location of hostname and private key for hidden service - Note:
#if not set, default is APPDIR/hiddenservice (~/.CoinSwapCS/hiddenservice)
#hs_dir = /chosen/directory
#port on which to serve clearnet
port = 7080
#whether to use SSL; non-SSL is *strongly* disrecommended, mainly because
#you lose confidentiality, it also allows MITM which is not a loss of funds risk,
#but again a loss of confidentiality risk. Note that client-side verification
#of cert is required to actually prevent MITM.
use_ssl = true
#directory containing private key and cert *.pem files; 0 means default location,
#which is homedir/ssl/ ; replace with fully qualified paths if needed.
ssl_private_key_location = 0
ssl_certificate_location = 0
#minimum and maximum allowable coinswap amounts, in satoshis;
#amounts to offer for coinswap
minimum_amount = 5000000
#note; if your balance in mixdepth 0 falls below this, the server will switch to
#busy state, and refuse further coinswaps until this changes. We do not change
#this value dynamically, as it would be a privacy leak.
maximum_amount = 500000000
#minimum and maximum allowable server and client locktimes (relative to current
#blockheight).
server_locktime_range = 10,50
client_locktime_range = 20,100
#client must choose the number of blocks to wait for confirmation of TX0, TX1.
tx01_confirm_range = 2, 4
#to reduce load/complexity, an upper limit on the number of concurrent coinswaps
maximum_concurrent_coinswaps = 3
#**FEES**
#Note that fees are by default collected across two different outputs in combination
#with other (probably much larger) amounts, so a small fee doesn't imply a dust
#output.
#The minimum acceptable fee in satoshis for a single coinswap
minimum_coinswap_fee = 100000
#Percentage fee for a coinswap (applied as long as it's higher than the above)
coinswap_fee_percent = 0.5
#**
#An amount used to blind/disconnect the two coin flows; setting it too small
#makes it too easy to correlate the transactions, making it too large could be
#a problem for your liquidity. In satoshis. Random value is chosen between
#maximum and minimum (unless too large for your wallet, then reduced but never
#below the minimum).
blinding_amount_min = 2000000
blinding_amount_max = 50000000
"""

def lookup_appdata_folder():
    from os import path, environ
    if sys.platform == 'darwin':
        if "HOME" in environ:
            data_folder = path.join(os.environ["HOME"],
                                   "Library/Application support/",
                                   global_singleton.APPNAME) + '/'
        else:
            print("Could not find home folder")
            os.exit()

    elif 'win32' in sys.platform or 'win64' in sys.platform:
        data_folder = path.join(environ['APPDATA'], global_singleton.APPNAME) + '\\'
    else:
        data_folder = path.expanduser(path.join("~",
                                    "." + global_singleton.APPNAME + "/"))
    return data_folder

def load_coinswap_config(config_path=None, bs=None):
    global_singleton.config.readfp(io.BytesIO(defaultconfig))
    if not config_path:
        global_singleton.homedir = lookup_appdata_folder()
    else:
        global_singleton.homedir = config_path
    if not os.path.exists(global_singleton.homedir):
        os.makedirs(global_singleton.homedir)
    #prepare folders for wallets and logs
    if not os.path.exists(os.path.join(global_singleton.homedir, "wallets")):
        os.makedirs(os.path.join(global_singleton.homedir, "wallets"))
    if not os.path.exists(os.path.join(global_singleton.homedir, "logs")):
        os.makedirs(os.path.join(global_singleton.homedir, "logs"))
    global_singleton.config_location = os.path.join(
        global_singleton.homedir, global_singleton.config_location)
    loadedFiles = global_singleton.config.read([global_singleton.config_location
                                               ])
    if len(loadedFiles) != 1:
        with open(global_singleton.config_location, "w") as configfile:
            configfile.write(defaultconfig)
    # configure the interface to the blockchain on startup
    global_singleton.bc_interface = get_blockchain_interface_instance(
        global_singleton.config)
    # set the console log level and initialize console logger
    try:
        global_singleton.console_log_level = global_singleton.config.get(
            "LOGGING", "console_log_level")
    except (NoSectionError, NoOptionError):
        print("No log level set, using default level INFO ")
    print("Setting console level to: ", global_singleton.console_log_level)
    consoleHandler.setLevel(global_singleton.console_log_level)
    log.addHandler(consoleHandler)
    #always uses segwit wallet (not optional)
    global_singleton.config.set("POLICY", "segwit", "true")
    #The underlying jmclient 'unconfirm_timeout_sec' config setting
    #in the TIMEOUT section is required for monitoring whether an
    #expected-to-be-broadcast tx is actually broadcast and seen on the
    #network. Although propagation_buffer is not *exactly* the same
    #concept, since that also applies for synchronising the seeing of
    #confirmations, it is a closely related delay. Using the same value
    #for both is sensible.
    global_singleton.config.set("TIMEOUT", "unconfirm_timeout_sec",
            global_singleton.config.get("TIMEOUT", "propagation_buffer"))
    #Confirm_timeout_hours on the other hand must be set arbitrarily
    #high; we can backout early in case of ultra slow confirmation (which
    #the code specifically avoids by setting high requirements on fees).
    global_singleton.config.set("TIMEOUT", "confirm_timeout_hours", "24")
    #inject the configuration to the underlying jmclient code.
    set_config(global_singleton.config, bcint=global_singleton.bc_interface)
    

def get_blockchain_interface_instance(_config):
    from jmclient import BitcoinCoreInterface, \
        RegtestBitcoinCoreInterface
    source = _config.get("BLOCKCHAIN", "blockchain_source")
    network = _config.get("BLOCKCHAIN", "network")
    testnet = network == 'testnet'
    rpc_host = _config.get("BLOCKCHAIN", "rpc_host")
    rpc_port = _config.get("BLOCKCHAIN", "rpc_port")
    rpc_user = _config.get("BLOCKCHAIN", "rpc_user")
    rpc_password = _config.get("BLOCKCHAIN", "rpc_password")
    rpc = JsonRpc(rpc_host, rpc_port, rpc_user, rpc_password)
    if source == 'bitcoin-rpc': #pragma: no cover
        #This cannot be tested without mainnet or testnet blockchain (not regtest)
        bc_interface = BitcoinCoreInterface(rpc, network)
    elif source == 'regtest':
        bc_interface = RegtestBitcoinCoreInterface(rpc)
    return bc_interface