#!/usr/bin/env python

'''What are you doing here? THIS IS TOP SECRET INFORMATION WE'VE GOT HERE!!!'''

import configparser
import logging
import re
import traceback
import urllib.request
from os.path import abspath, dirname
from appJar import gui
import i18n
from i18n import t
from sshtunnel import SSHTunnelForwarder
import sys


logging.basicConfig(format='[%(name)-6s] [%(levelname)-8s] %(message)s',
                    level=logging.DEBUG)

LOGGER = logging.getLogger('main')
APP = gui()

config = configparser.ConfigParser()

I18N_PATH = dirname(abspath(__file__)) + '/locale'
i18n.load_path.append(I18N_PATH)


# just a lazy place to store things at the moment. i'm sorry.
DEFAULTS = {
    'optionsfile': 'options.ini',
    'relay': '67328286-288b-4d7d-b637-3fe2ae63abf2.pub.cloud.scaleway.com:22',
    'password': 'borer',
    'username': 'port',
    'localport': '25565',
    'localhost': 'localhost',
}

def readconfigfile():
    config.read(DEFAULTS['optionsfile'])
    # ADD these sections if they are not already present (could be cleaned up by creating a dedicated function, but i'm lazy)
    if 'GENERAL' not in config:
        config['GENERAL'] = {}
    if 'COLOURS' not in config:
        config['COLOURS'] = {}
    if 'FG' not in config['COLOURS']:
        config['COLOURS']['FG'] = '#A4A4A4'
    if 'BG' not in config['COLOURS']:
        config['COLOURS']['BG'] = '#404040'
    if 'FETCHRELAYSFROM' not in config['GENERAL']:
        config['GENERAL']['FETCHRELAYSFROM'] = 'https://raw.githubusercontent.com/FlaggedBrad/Port-Borer/master/servers.txt'
    if 'NEWRELAYSAUTOMATICALLYACTIVE' not in config['GENERAL']:
        config['GENERAL']['NEWRELAYSAUTOMATICALLYACTIVE'] = 'True'
    if 'WEBRELAYS' not in config:
        config['WEBRELAYS'] = {}
    if 'CUSTOMRELAYS' not in config:
        config['CUSTOMRELAYS'] = {}

    #announce that we are trying to download the relay list
    LOGGER.debug('Downloading Relay List...')
    # Try to download and parse the relay list from URL in config
    try:
        WEBRELAYS = urllib.request.urlopen(config['GENERAL']['FETCHRELAYSFROM']).read().decode('utf-8').splitlines()
    # if it errors, spit out that something went wrong.
    except Exception:
        LOGGER.debug('Relay List download from '+config['GENERAL']['FETCHRELAYSFROM']+' FAILED!')
    # if it doesn't error, do the below actions.
    else:
        LOGGER.debug('Relay List download from '+config['GENERAL']['FETCHRELAYSFROM']+' SUCCEDED!')
        # save downloaded relays
        for relay in WEBRELAYS:
            if relay not in config['WEBRELAYS']:
                LOGGER.debug('Relay "'+relay+'" Not found in "'+DEFAULTS['optionsfile']+'"')
                config['WEBRELAYS'][relay] = config['GENERAL']['NEWRELAYSAUTOMATICALLYACTIVE']
        # delete relays that are found in config but not found in downloaded relay list
        for relay in config['WEBRELAYS']:
            if relay not in WEBRELAYS:
                LOGGER.debug('Removing Relay "'+relay+'" Because it was not in the downloaded relay list.')
                config.remove_option('WEBRELAYS', relay)

    saveconfigfile()


def saveconfigfile():
    config.write(open(DEFAULTS['optionsfile'], 'w'))

# There is no way to set the background for all elements of a certain type, so i must do it on my own...
def setbuttonparams(name):
    APP.setButtonFg(name, config['COLOURS']['Fg'])
    APP.setButtonBg(name, config['COLOURS']['Bg'])

def setentryparams(name):
    APP.setEntryBg(name, config['COLOURS']['Bg'])
    APP.setEntryFg(name, config['COLOURS']['Fg'])

def setlabelparams(name):
    APP.setLabelFg(name, config['COLOURS']['Fg'])
    APP.setLabelBg(name, config['COLOURS']['Bg'])

def setcolours():
    LOGGER.debug('Setting colours for Bg+Fg.')
    APP.setFg(config['COLOURS']['Fg'])
    APP.setBg(config['COLOURS']['Bg'])



'''All functions below are for the launch function to use'''

def addrelaylistentry(height, relay, configsection):
    LOGGER.debug(relay+' = '+config[configsection][relay])
    APP.setSticky('w')
    APP.setStretch('both')
    APP.addCheckBox(relay, height, 0, 2)
    APP.setCheckBoxChangeFunction(relay, relaycheckbox)
    APP.setCheckBox(relay, ticked=config[configsection][relay], callFunction=True)
    APP.setCheckBoxFg(relay, config['COLOURS']['Fg'])
    APP.setCheckBoxBg(relay, config['COLOURS']['Bg'])
    APP.setSticky('ew')
    APP.setStretch('row')
    APP.addLabel(relay+' - LABELms', onsubmit, height, 2)
    APP.setLabel(relay+' - LABELms', '9999ₘₛ')
    setlabelparams(relay+' - LABELms')
    APP.setLabelWidth(relay+' - LABELms', 5)



'''All functions above are for the launch function to use'''



# Build (refresh) the relay servers once more
def reloadrelays():
    APP.openLabelFrame(t('label.relaylist'))
    LOGGER.debug('===== Building '+t('label.remoteconnsec')+' =====')
    APP.setSticky('new')
    APP.startFrame('holder', 0, 0, 2)
    setcolours()

    APP.setSticky('w')
    scrollpanebuffer = 1000
    # Build Custom(user-gathered) Relay server checkboxes
    count = len(config['CUSTOMRELAYS'])+scrollpanebuffer
    for relay in config['CUSTOMRELAYS']:
        if relay not in config['WEBRELAYS']:
            LOGGER.debug('\'addrelaylistentry\' with: count='+str(count)+', relay='+relay+', configsection=CUSTOMRELAYS')
            addrelaylistentry(count, relay, 'CUSTOMRELAYS')
            APP.addButton(relay+' - BUTTON❌', relaybuttons, count, 1).config(font=('', '9'))
            setbuttonparams(relay+' - BUTTON❌')
            APP.setButton(relay+' - BUTTON❌', '❌')
            APP.setButtonRelief(relay+' - BUTTON❌', 'flat')
            count -= 1
        else:
            LOGGER.debug('Removing \"'+relay+'\" from CUSTOMRELAYS section of options, because it was found in the web-relay list')
            config.remove_option('CUSTOMRELAYS', relay)
    # Build Public(web-gathered) Relay server checkboxes
    count = len(config['CUSTOMRELAYS'])+len(config['WEBRELAYS'])+scrollpanebuffer
    for relay in config['WEBRELAYS']:
        LOGGER.debug('\'addrelaylistentry\' with: count='+str(count)+', relay='+relay+', configsection=WEBRELAYS')
        addrelaylistentry(count, relay, 'WEBRELAYS')
        APP.addLabel(relay+' - LABELBLANK', '', count, 1).config(font=('', '9'))
        setlabelparams(relay+' - LABELBLANK')
        count -= 1

    heightcalc = 20*(len(config['CUSTOMRELAYS'])+len(config['WEBRELAYS']))+60
    LOGGER.debug('estimated height calculation is \''+str(heightcalc)+'\'')
    if heightcalc > 157:
        if heightcalc < 700:
            APP.setGeometry(500, heightcalc)
    else:
        APP.setGeometry(500, 157)
    try:
        APP.setLocation(int(config['GENERAL']['POSX']), int(config['GENERAL']['POSY']))
        LOGGER.debug('GUI Position read from config file. Setting GUI to location')
    except KeyError:
        LOGGER.debug('Position has not been set yet. Using default GUI location')
    APP.topLevel.resizable(width=False, height=True)

    APP.stopFrame()

# Select/Deselect-all buttons
    APP.setStretch('column')
    LOGGER.debug('Adding Selection Buttons')
    APP.setSticky('sew')
    APP.addButton(t('label.selectall'), onsubmit, 2, 0).config(font=('', '9'))
    setbuttonparams(t('label.selectall'))
    APP.addButton(t('label.deselectall'), onsubmit, 2, 1).config(font=('', '9'))
    setbuttonparams(t('label.deselectall'))

    LOGGER.debug('=== Done Building '+t('label.remoteconnsec')+' ===')
    APP.stopLabelFrame()


def launch():
    readconfigfile()
    '''open the app.'''
    
    LOGGER.debug('Setting up main GUI')
    APP.setTitle(t('label.title'))
    APP.setFg(config['COLOURS']['Fg'], override=True)
    APP.setBg(config['COLOURS']['Bg'], override=True)
    #APP.setResizable(canResize=False)
    if sys.platform == 'win32':
        APP.setIcon('icon.ico')
    else:
        APP.setIcon('icon.gif')

    # this allows the relay section to "hug" this element, because it expands to the bottom-most collumn on the right of it (i don't think there is a way of stretching it to the bottom with flags or options)
    APP.addLabel('brfiller', ' ', 100, 20)



    # Start a frame with a label to hold everything on the left inside of a container.
    APP.setStretch('both')
    APP.setSticky('sew')
    APP.startLabelFrame(t('label.relaylist'), 0, 0, 1, 110)
    setcolours()
    APP.stopLabelFrame()
    reloadrelays()

# Right-Side elements
    APP.setSticky('n')


# Local Section
    APP.setStretch('none')
    APP.startLabelFrame(t('label.localconnsec'), 0, 20)
    # apply widget "gravity" inside of this frame to the east(right).
    APP.setSticky('e')
    # Bind port entry
    APP.addNumericEntry(t('label.localport'), 1, 1, 20)
    APP.setEntryChangeFunction(t('label.localport'), onchange)
    APP.setEntrySubmitFunction(t('label.localport'), onsubmit)
    setentryparams(t('label.localport'))
    APP.addButton(t('label.bindlocal'), onsubmit, 1, 2)
    setbuttonparams(t('label.bindlocal'))
    APP.setButtonSubmitFunction(t('label.bindlocal'), onsubmit)
    APP.stopLabelFrame()

# Custom Relay Section
    APP.startLabelFrame(t('label.remoteconnsec'), 1, 20)
    # Label
    APP.addLabel(t('label.addcustrelay'), t('label.addcustrelay'), 1, 1)
    # apply widget "gravity" inside of this frame to the east(right).
    APP.setSticky('e')
    # Private Relay Server
    APP.addEntry(t('label.addcustrelay'), 2, 1)
    setentryparams(t('label.addcustrelay'))
    APP.addButton(t('label.remoteconnsec')+'➕', onsubmit, 2, 1)
    setbuttonparams(t('label.remoteconnsec')+'➕')
    APP.setButton(t('label.remoteconnsec')+'➕', '➕')
    APP.stopLabelFrame()


# START THE GUI!!!1
    LOGGER.debug('Starting GUI')
    APP.setStopFunction(closegui)
    APP.go()


def closegui():
    LOGGER.debug('Saving GUI geometry: '+APP.topLevel.geometry())
    regexmatch = re.match('^(-?\d+)x(-?\d+)\+(-?\d+)\+(-?\d+)$', APP.topLevel.geometry())
    # config['GENERAL']['WIDTH'] = regexmatch.group(1)
    # config['GENERAL']['HEIGHT'] = regexmatch.group(2)
    config['GENERAL']['POSX'] = regexmatch.group(3)
    config['GENERAL']['POSY'] = regexmatch.group(4)
    saveconfigfile()
    LOGGER.debug('Closing GUI')
    return(True)



def loop():
    print('loop')




'''Handle widget changes(every time changed/edited)'''
def onchange(widgetname):
    if widgetname == t('label.relaylist'):
        widgetname

    if widgetname == t('label.localport'):
        if int(APP.getEntry(widgetname)) != 0:
            APP.setEntry(widgetname, int(APP.getEntry(widgetname)))
            if int(APP.getEntry(widgetname)) > 65535:
                APP.setEntry(widgetname, int(APP.getEntry(widgetname)) // 10)
                APP.bell()
        else:
            APP.setEntry(widgetname, '')

        LOGGER.debug('Saving local connection port entry value \''+str(int(APP.getEntry(widgetname)))+'\' to config file')
        config['GENERAL']['LOCALBIND'] = str(int(APP.getEntry(widgetname)))
        saveconfigfile()

'''Handles relay toggleboxes(onchange)'''
def relaycheckbox(relay):
    LOGGER.debug('Checkbox toggled! Saving '+relay+' as '+str(APP.getCheckBox(relay)))
    if relay in config['CUSTOMRELAYS']:
        config['CUSTOMRELAYS'][relay] = str(APP.getCheckBox(relay))
    if relay in config['WEBRELAYS']:
        config['WEBRELAYS'][relay] = str(APP.getCheckBox(relay))
    saveconfigfile()


def relaybuttons(button):
    # remove relay from list of custom relays
    if button.endswith(' - BUTTON❌'):
        relay = re.search('^(.*) - BUTTON❌$', button).group(1)
        print(relay)



'''Handle widget Submissions.'''
def onsubmit(widgetname):
    # Bind Local Connection handler
    if widgetname == t('label.bindlocal') or t('label.localport'):
        if APP.getEntry(t('label.localport')) != 0:
            LOGGER.debug('Binding local port to port \''+str(int(APP.getEntry(t('label.localport'))))+'\'')
            config['GENERAL']['LOCALBIND'] = str(int(APP.getEntry(t('label.localport'))))
            saveconfigfile()
        # need to do more things here, it currently only saves the text entry to the config. I need the gui to load the last port once it's opened.

    # Select/Deleselect all handler
    if widgetname == t('label.selectall'):
        LOGGER.debug('Selecting all checkboxes')
        config['GENERAL']['NEWRELAYSAUTOMATICALLYACTIVE'] = 'True'
        for section in ['CUSTOMRELAYS', 'WEBRELAYS']:
            for relay in config[section]:
                APP.setCheckBox(relay, ticked=True)
        saveconfigfile()
    if widgetname == t('label.deselectall'):
        LOGGER.debug('Deselecting all checkboxes')
        config['GENERAL']['NEWRELAYSAUTOMATICALLYACTIVE'] = 'False'
        for section in ['CUSTOMRELAYS', 'WEBRELAYS']:
            for relay in config[section]:
                APP.setCheckBox(relay, ticked=False)
        saveconfigfile()




def loginsubmit(_):
    '''Save login credentials for clicked relay.'''
    APP.hide()

    relay = APP.getEntry(t('label.relay'))
    username = APP.getEntry(t('label.username'))
    password = APP.getEntry(t('label.password'))
    remember = APP.getCheckBox(t('label.rememberme'))
    LOGGER.debug('Relay: {relay} ({type(relay)})')
    # LOGGER.debug('User: {username} ({type(username)})')
    # LOGGER.debug('Pass: {password} ({type(password)})')
    LOGGER.debug('Remember: {remember} ({type(remember)})')

    APP.removeAllWidgets()
    APP.disableEnter()
    APP.setResizable(canResize=True)

    regexsearch = re.search('^(.*):(\d*)$', relay)
    

    APP.show()


# This gets called when you double-click a server and the default password or username is incorrect. (still a W.I.P. and most likely unimplemented)
def loginscreen():
    LOGGER.debug('Setting up the Login box')
    APP.addLabelEntry(t('label.username'), 1, 1, 2)
    APP.addLabelSecretEntry(t('label.password'), 2, 1, 2)
    APP.addCheckBox(t('label.rememberme'), 3, 1)
    APP.setCheckBox(t('label.rememberme', ticked=True))
    APP.addButton(t('label.login'), loginsubmit, 3, 2, 2)
    setbuttonparams(t('label.login'))
    APP.enableEnter(loginsubmit)
    APP.setResizable(canResize=False)
    APP.setEntryDefault(t('label.relay'), DEFAULTS['relay'])


if __name__ == '__main__':
    try:
        launch()
    except Exception as exc:
        LOGGER.exception('A problem has occurred:')
