# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
# and is covered by GPLv3 license found in COPYING.
#
# main.py
#
# - main.loop is imported and "run forever" by boot.py, forced into place by COLDCARD/initfs code
# - cannot be changed by /flash/lib overrides, because already imported before that.
#

# see RAM_HEADER_BASE, and coldcardFirmwareHeader_t in sigheader.h
import pyb, sys, version, gc

# this makes the GC run when larger objects are free in an attempt to reduce fragmentation.
gc.threshold(4096)

# what firmware signing key did we boot with? are we in dev mode?
is_devmode = version.is_devmode()

if 0:
    # useful for debug: keep this stub
    from usb import enable_usb
    enable_usb(loop, True)
    raise SystemExit

if is_devmode:
    # For devs only: allow code in this directory to overide compiled-in stuff. Dangerous!
    # - using relative paths here so works better on simulator
    sys.path.insert(0, 'flash/lib')

    # Give external devs a way to start stuff early, but not before this point
    try:
        import boot2
    except: pass


import ckcc
import uasyncio.core as asyncio

loop = asyncio.get_event_loop()

print("---\nColdcard Wallet from Coinkite Inc. (c) 2018.\n")

# Setup OLED and get something onto it.
from display import Display, FontFixed, FontSmall, FontLarge
dis = Display()
dis.splash()

if version.is_mark2():
    # Setup membrane numpad (mark 2)
    from mempad import MembraneNumpad
    numpad = MembraneNumpad(loop)
else:
    # Setup touch numpad (mark 1 hardware)
    from touchpad import TouchNumpad
    numpad = TouchNumpad(loop)

# Serial Flash memory
from sflash import SPIFlash
sf = SPIFlash()

# NV settings
from nvstore import SettingsObject
settings = SettingsObject(loop)

# important default/restore preference
numpad.sensitivity = settings.get('sens', numpad.sensitivity)


async def done_splash2():
    # Boot up code; after splash screen is done.
                
    # MAYBE: check if we're a brick and die again? Or show msg?

    if version.is_factory_mode():
        # in factory mode, turn on USB early to allow debug/setup
        from usb import enable_usb
        enable_usb(loop, True)

        # always start the self test.
        if not settings.get('tested', False):
            from actions import start_selftest
            await start_selftest()

    else:
        # force them to accept terms (unless marked as already done)
        from actions import accept_terms
        await accept_terms()

    # Prompt for PIN and then pick appropriate top-level menu,
    # based on contents of secure chip (ie. is there
    # a wallet defined)
    from actions import start_login_sequence
    await start_login_sequence()

    loop.create_task(mainline())

async def mainline():
    # Mainline of program, after startup
    #
    # - Do not add to this function, its vars are
    #   in memory forever; instead, extend done_splash2 above.
    from ux import the_ux

    gc.collect()
    #print("Free mem: %d" % gc.mem_free())

    while 1:
        await the_ux.interact()

# Setup to start the splash screen.
dis.splash_animate(loop, done_splash2, numpad.capture_baseline)

# Some background "tasks"
#
from dev_helper import monitor_usb
loop.create_task(monitor_usb())

from ux import idle_logout
loop.create_task(idle_logout())

from files import CardSlot
CardSlot.setup()

# This "pa" object holds some state shared w/ bootloader about the PIN
try:
    from pincodes import PinAttempt

    pa = PinAttempt()
    pa.setup(b'')       # just to see where we stand.
except RuntimeError as e:
    print("Problem: %r" % e)

def go():
    # Wrapper for better error handling/recover at top level.
    #
    try:
        loop.run_forever()
    except BaseException as exc:
        from usb import is_vcp_active
        is_debug = is_vcp_active()

        if is_debug and isinstance(exc, KeyboardInterrupt):
            # preserve GUI state, but want to see where we are
            print("KeyboardInterrupt")
            raise
        elif isinstance(exc, SystemExit):
            # Ctrl-D and warm reboot cause this, not bugs
            raise
        else:
            # show stacktrace for debug photos
            try:
                import uio, ux
                tmp = uio.StringIO()
                sys.print_exception(exc, tmp)
                msg = tmp.getvalue()
                del tmp
                print(msg)
                ux.show_fatal_error(msg)
            except: pass

            # securely die (wipe memory)
            if not is_debug:
                try:
                    import callgate
                    callgate.show_logout(1)
                except: pass

if is_devmode:
    # Give external devs a way to start semi-early.
    try:
        import main2
    except: pass

# EOF
