#!/bin/false
# not executable.

"""
Informatics-Coin NEP5

DOGE war gestern. BUY FSINF! FOMO! ALL-IN!
"""

from boa.interop.Neo.Runtime import GetTrigger, CheckWitness
from boa.interop.Neo.Action import RegisterAction
from boa.interop.Neo.TriggerType import Application, Verification
from boa.interop.Neo.Storage import GetContext, Get, Put, Delete, Find
from boa.interop.System.ExecutionEngine import GetCallingScriptHash
from boa.interop.Neo.Blockchain import GetContract
from boa.interop.Neo.Contract import Migrate, Destroy
from boa.interop.Neo.Iterator import IterNext, IterKey, IterValue
from boa.builtins import concat

# -------------------------------------------
# TOKEN SETTINGS
# -------------------------------------------

# Script hash of the contract owner
OWNER = b'\x0e\x42\x28\xf9\x68\x68\x07\x9d\xaa\xd2\xa5\xa8\x3e\x93\xab\xbe\x5c\xaf\x22\x1d'

# Name of the Token
TOKEN_NAME = 'Informatics token v2'

# Symbol of the Token
TOKEN_SYMBOL = 'Inf'

# Number of decimal places
TOKEN_DECIMALS = 2


# -------------------------------------------
# Events
# -------------------------------------------

OnTransfer = RegisterAction('transfer', 'addr_from', 'addr_to', 'amount')
OnError = RegisterAction('error', 'message')


def Main(operation, args):
    """
    This is the main entry point for the Smart Contract

    :param operation: the operation to be performed ( eg `balanceOf`, `transfer`, etc)
    :type operation: str
    :param args: a list of arguments ( which may be empty, but not absent )
    :type args: list
    :return: indicating the successful execution of the smart contract
    :rtype: bool
    """

    # The trigger determines whether this smart contract is being
    # run in 'verification' mode or 'application'
    trigger = GetTrigger()

    # 'Verification' mode is used when trying to spend assets ( eg NEO, Gas)
    # on behalf of this contract's address
    if trigger == Verification():

        # if the script that sent this is the owner
        # we allow the spend
        assert CheckWitness(OWNER), 'Unauthorized'
        return True


    # 'Application' mode is the main body of the smart contract
    elif trigger == Application():

        if operation == 'name':
            return TOKEN_NAME

        elif operation == 'decimals':
            return TOKEN_DECIMALS

        elif operation == 'symbol':
            return TOKEN_SYMBOL

        elif operation == 'totalSupply':
            return do_total_supply(ctx=GetContext())

        elif operation == 'balanceOf':
            assert len(args) == 1, 'Incorrect argument count'
            return do_balance_of(ctx=GetContext(), _account=args[0])

        elif operation == 'transfer':
            assert len(args) == 3, 'Incorrect argument count'
            return do_transfer(ctx=GetContext(), _from=args[0], _to=args[1], _amount=args[2], caller=GetCallingScriptHash())

        # the following are administrative methods

        elif operation == 'owner_mint':
            assert len(args) == 2, 'Incorrect argument count'
            return owner_mint(ctx=GetContext(), _to=args[0], _amount=args[1])

        elif operation == 'owner_burn':
            assert len(args) == 1, 'Incorrect argument count'
            return owner_burn(ctx=GetContext(), _amount=args[0])

        elif operation == 'owner_migrate_contract':
            assert len(args) == 9, 'Incorrect argument count'
            return owner_migrate(ctx=GetContext(), _script=args[0], _param_list=args[1], _return_type=args[2], _properties=args[3], _name=args[4], _version=args[5], _author=args[6], _email=args[7], _description=args[8], caller=GetCallingScriptHash())

        elif operation == 'owner_destroy_contract':
            assert CheckWitness(OWNER), 'Unauthorized'
            return Destroy()

        elif operation == 'owner_dump':
            return owner_dump(ctx=GetContext())

    assert False, 'Unknown operation'



def do_total_supply(ctx):
    """
    Method to return the total supply of this coin

    :return: the total supply of this coin
    :rtype: int

    """

    res = Get(ctx, "totalSupply")
    return res



def do_balance_of(ctx, _account):
    """
    Method to return the current balance of an address

    :param _account: the account address to retrieve the balance for
    :type _account: bytearray

    :return: the current balance of an address
    :rtype: int

    """

    assert len(_account) == 20, "Invalid address"
    return Get(ctx, _account)



def do_transfer(ctx, _from, _to, _amount, caller):
    """
    Method to transfer NEP5 tokens of a specified _amount from one account to another

    :param _from: the address to transfer from
    :type _from: bytearray
    :param _to: the address to transfer to
    :type _to: bytearray
    :param _amount: the _amount of NEP5 tokens to transfer
    :type _amount: int
    :param caller: the scripthash of the calling script
    :type caller: bytearray

    :return: whether the transfer was successful
    :rtype: bool

    """

    assert _amount > 0, "Invalid amount"
    assert len(_from) == 20, "Invalid from address"
    assert len(_to) == 20, "Invalid to address"
    assert CheckWitnessOrCaller(_from, caller), "Transfer unauthorized"

    if _from == _to:
        print("Transfer to self")
        return True

    from_balance = Get(ctx, _from)
    assert from_balance >= _amount, "Insufficient funds"

    if from_balance == _amount:
        print("Removing all funds!")
        Delete(ctx, _from)

    else:
        difference = from_balance - _amount
        Put(ctx, _from, difference)

    to_value = Get(ctx, _to)
    to_total = to_value + _amount
    Put(ctx, _to, to_total)

    OnTransfer(_from, _to, _amount)
    return True



def owner_mint(ctx, _to, _amount):
    """
    Method to mint tokens

    :return: whether the token minting was successful
    :rtype: bool

    """

    assert CheckWitness(OWNER), 'Unauthorized'
    assert len(_to) == 20, "Invalid to address"
    assert _amount > 0, "Invalid amount"

    balance = Get(ctx, _to)
# to discuss for neo3.
#    if balance == 0:
#        Put(ctx, concat(_to,b'--OM'), 1)
    new_total = balance + _amount
    Put(ctx, _to, new_total)

    total_supply = Get(ctx, 'totalSupply')
    new_total_supply = total_supply + _amount
    Put(ctx, 'totalSupply', new_total_supply)

    OnTransfer(None, _to, _amount)
    return True



def owner_burn(ctx, _amount):
    """
    Method to burn tokens

    :param _amount: the _amount of NEP5 tokens to transfer
    :type _amount: int

    :return: whether the token burning was successful
    :rtype: bool

    """

    assert CheckWitness(OWNER), 'Unauthorized'
    assert _amount > 0, "Invalid amount"

    from_val = Get(ctx, OWNER)
    assert from_val >= _amount, "Insufficient funds"

    if from_val == _amount:
        print("Removing all funds!")
        Delete(ctx, OWNER)

    else:
        difference = from_val - _amount
        Put(ctx, OWNER, difference)

    total_supply = Get(ctx, 'totalSupply')
    new_total_supply = total_supply - _amount
    Put(ctx, 'totalSupply', new_total_supply)


    OnTransfer(OWNER, None, _amount)
    return True



def owner_migrate(ctx, _script, _param_list, _return_type, _properties, _name, _version, _author, _email, _description, caller):
    """
    Migrate this contract to a new version

    :param args: list of arguments containing migrating contract info
    :return: Contract
    """
    assert CheckWitness(OWNER), 'Unauthorized'

    # once migrated, funds held at the contract address are no
    # longer retrievable.  make sure to transfer all funds
    # before doing anything.
    current_contract_balance = Get(ctx, caller)
    if current_contract_balance > 0:
        print("Cannot migrate yet. Please transfer all neo/gas and tokens from contract address")
        return False

    Migrate(_script, _param_list, _return_type, _properties, _name, _version, _author, _email, _description)
    return True



def owner_dump(ctx):
    """
    Dump contract store

    :param args: list of arguments containing migrating contract info
    :return: Contract
    """
    assert CheckWitness(OWNER), 'Unauthorized'

    items = []
    result_iter = Find(ctx, "")
    while result_iter.IterNext():
        items.append([result_iter.IterKey(), result_iter.IterValue()])
    return items



def CheckWitnessOrCaller(scripthash, caller):
    """
    Method to check if the transaction is signed by a private key or is the scripthash of a contract that is authorized to perform the requested function for its own address only

    :param scripthash: the scripthash to be checked
    :type scripthash: bytearray
    :param caller: the scripthash of the calling script
    :type caller: bytearray

    :return: whether the scripthash is authorized
    :rtype: bool

    """

    if GetContract(scripthash):
        if scripthash == caller:
            return True  # a contract can spend its own funds
        else:
            # deny third-party contracts from transferring
            # tokens of a user even with the user signature
            # (this will break ability of some DEX to list the token)
            return False

    return CheckWitness(scripthash)



def AssertionError(msg):
    """
    Method to throw an exception (required by assert) - will log a notification to the neo-cli ApplicationLog to aid in post-transaction troubleshooting and analysis

    :param msg: error message
    :type msg: string
    """

    OnError(msg)
    raise Exception(msg)
