import json

class SiaWallet(object):
    """
    The wallet stores and manages siacoins. The wallet's API endpoints expose methods for creating and loading wallets, locking and unlocking, sending siacoins, and getting the wallet's balance.
    """
    def __init__(self, scpy):
        self.scpy = scpy

    def __call__(self):
        """
        Returns basic information about the wallet, such as whether the wallet is locked or unlocked

        :return: Dict with information
        """
        return self.scpy.get_api('/wallet')

    def gen_address(self):
        """
        Gets a new address from the wallet generated by the primary seed. An error will be returned if the wallet is locked.

        :return: String with the new address
        """
        return self.scpy.get_api('/wallet/address')['address']

    @property
    def addresses(self):
        """
        List of addresses from the wallet.
        """
        return self.scpy.get_api('/wallet/addresses')['addresses']

    def backup(self, dest):
        """
        Creates a backup of the wallet settings file. Though this can easily be done manually, the settings file is often in an unknown or difficult to find location. The /wallet/backup call can spare users the trouble of needing to find their wallet file. The destination file is overwritten if it already exists.

        :param dest: Path to the location on disk where the backup file will be saved
        :type dest: str
        :return: True if action succeeded
        :raises: SiaError if action was unsuccessful
        """
        return self.scpy.get_api('/wallet/address', params={'destination': dest})

    def init(self, password, dictionary='english', force=False):
        """
        Initializes the wallet. After the wallet has been initialized once, it does not need to be initialized again, and future calls to /wallet/init will return an error, unless the force flag is set. The encryption password is provided by the api call. If the password is blank, then the password will be set to the same as the seed.

        :param password: Password that will be used to encrypt the wallet. All subsequent calls should use this password. If left blank, the seed that gets returned will also be the encryption password
        :type password: str
        :param dictionary: Name of the dictionary that should be used when encoding the seed. 'english' is the most common choice when picking a dictionary
        :type dictionary: str
        :param force: When set to true /wallet/init will Reset the wallet if one exists instead of returning an error. This allows API callers to reinitialize a new wallet
        :type force: bool
        :return: Wallet seed used to generate addresses that the wallet is able to spend
        """
        return self.scpy.post_api('/wallet/init',
                                  data={'encryptionpassword': password, 'dictionary': dictionary, 'force': force})['primaryseed']

    def init_with_seed(self, password, seed, dictionary='english', force=False):
        """
        Initializes the wallet using a preexisting seed. After the wallet has been initialized once, it does not need to be initialized again, and future calls to /wallet/init/seed will return an error unless the force flag is set. The encryption password is provided by the api call. If the password is blank, then the password will be set to the same as the seed. Note that loading a preexisting seed requires scanning the blockchain to determine how many keys have been generated from the seed. For this reason, /wallet/init/seed can only be called if the blockchain is synced.

        :param password: Password that will be used to encrypt the wallet. All subsequent calls should use this password. If left blank, the seed that gets returned will also be the encryption password
        :type password: str
        :param seed: Dictionary-encoded phrase that corresponds to the seed being used to initialize the wallet
        :type seed: str
        :param dictionary: Name of the dictionary that should be used when encoding the seed. 'english' is the most common choice when picking a dictionary
        :type dictionary: str
        :param force: When set to true /wallet/init will Reset the wallet if one exists instead of returning an error. This allows API callers to reinitialize a new wallet
        :type force: bool
        :return: True if action succeeded
        :raises: SiaError if action was unsuccessful
        """
        return self.scpy.post_api('/wallet/init/seed',
                                  data={'encryptionpassword': password, 'dictionary': dictionary, 'force': force})

    def add_seed(self, seed, password, dictionary='english'):
        """
        Gives the wallet a seed to track when looking for incoming transactions. The wallet will be able to spend outputs related to addresses created by the seed. The seed is added as an auxiliary seed, and does not replace the primary seed. Only the primary seed will be used for generating new addresses.

        :param seed: Dictionary-encoded phrase that corresponds to the seed being used to initialize the wallet.
        :type seed: str
        :param password: Key used to encrypt the new seed when it is saved to disk
        :type password: str
        :param dictionary: Name of the dictionary that should be used when encoding the seed. 'english' is the most common choice when picking a dictionary
        :type dictionary: str
        :return: True if action succeeded
        :raises: SiaError if action was unsuccessful
        """
        return self.scpy.post_api('/wallet/seed',
                                  data={'encryptionpassword': password, 'dictionary': dictionary, 'seed': seed})

    def get_seeds(self, dictionary='english'):
        """
        Returns a list of seeds in use by the wallet. The primary seed is the only seed that gets used to generate new addresses. This call is unavailable when the wallet is locked.

        :param dictionary: Name of the dictionary that should be used when encoding the seed. 'english' is the most common choice when picking a dictionary
        :type dictionary: str
        :return: Dict with primary seed, number of addresses remaining and auxiliary seed list
        """
        return self.scpy.get_api('/wallet/seeds', data={'dictionary': dictionary})

    def send_siacoins(self, amount, destination):
        """
        Send siacoins to an address. The outputs are arbitrarily selected from addresses in the wallet.

        :param amount: Number of hastings being sent. A hasting is the smallest unit in Sia. There are 10^24 hastings in a siacoin
        :type amount: int
        :param destination: Address that is receiving the coins
        :type destination: str
        :return: Array of IDs of the transactions that were created when sending the coins
        """
        return self.scpy.post_api('/wallet/siacoins',
                                  data={'amount': amount, 'destination': destination})['transactionids']

    def send_to_many(self, outputs):
        """
        Send siacoins to a set of addresses. The outputs are arbitrarily selected from addresses in the wallet. The number of outputs should not exceed 400; this may result in a transaction too large to fit in the transaction pool.

        :param outputs: Array of (dest, amount) tuples
        :type outputs: list
        :return: Array of IDs of the transactions that were created when sending the coins
        """
        outputs = []
        for transaction in list:
            outputs.append({'unlockhash': transaction[0], 'value': transaction[1]})
        return self.scpy.post_api('/wallet/siacoins',
                                  data={'outputs': json.dumps(outputs)})['transactionids']

    def sweep(self, seed, dictionary='english'):
        """
        Scan the blockchain for outputs belonging to a seed and send them to an address owned by the wallet.

        :param seed: Dictionary-encoded phrase that corresponds to the seed being added to the wallet.
        :type seed: str
        :param dictionary: Name of the dictionary that should be used when decoding the seed. 'english' is the most common choice when picking a dictionary.
        :type dictionary: str
        :return: Amount of siacoins in hastings that were transferred to the wallet as a result of the sweep
        """
        return self.scpy.post_api('/wallet/sweep/seed', data={'seed': seed, 'dictionary': dictionary})['coins']

    def lock(self):
        """
        Locks the wallet, wiping all secret keys. After being locked, the keys are encrypted. Queries for the seed, to send siafunds, and related queries become unavailable. Queries concerning transaction history and balance are still available.

        :return: True if action succeeded
        :raises: SiaError if action was unsuccessful
        """
        return self.scpy.post_api('/wallet/lock')

    def transaction(self, id):
        """
        Gets the transaction associated with a specific transaction id.

        :param id: ID of the transaction being requested
        :type id: str
        :return: Dict with transaction details
        """
        return self.scpy.get_api(f'/wallet/transaction/{id}')['transaction']

    def transactions(self, startheight, endheight):
        """
        Returns a list of transactions related to the wallet.

        :param startheight: Height of the block where transaction history should begin
        :type startheight: int
        :param endheight: eight of of the block where the transaction history should end. If 'endheight' is greater than the current height, all transactions up to and including the most recent block will be provided
        :type endheight: int
        :return: Dict with confirmed and unconfirmed transactions
        """
        return self.scpy.get_api('/wallet/transactions', params={'startheight': startheight, 'endheight': endheight})

    def transactions_by_address(self, address):
        """
        Returns all of the transactions related to a specific address.

        :param address: Wallet address whose transactions are being requested
        :type address: str
        :return: Array of processed transactions that relate to the supplied address
        """
        return self.scpy.get_api(f'/wallet/transactions/{address}')['transactions']

    def unlock(self, password):
        """
        Unlocks the wallet. The wallet is capable of knowing whether the correct password was provided.

        :param password: Password that gets used to decrypt the file. Most frequently, the encryption password is the same as the primary wallet seed.
        :type password: str
        :return: True if action succeeded
        :raises: SiaError if action was unsuccessful
        """
        return self.scpy.post_api('/wallet/unlock', data={'encryptionpassword': password})

    def is_address_valid(self, address):
        """
        Checks if address provided is a valid UnlockHash

        :param address: Address to check
        :type address: str
        :return: Boolean, whether address is valid or not
        """
        return self.scpy.get_api(f'/wallet/verify/address/{address}')['valid']

    def change_password(self, old, new):
        """
        Changes the wallet's encryption password.

        :param old: Wallet's current encryption password
        :type old: str
        :param new: New password for the wallet
        :type new: str
        :return: True if action succeeded
        :raises: SiaError if action was unsuccessful
        """
        return self.scpy.post_api('/wallet/changepassword', data={'encryptionpassword': old, 'newpassword': new})
