# Part of flinks
# (C) Martin Bays 2008
# Released under the terms of the GPLv3

import os,sys
from string import *

from io import StringIO

import curses
import curses.ascii as ascii

import re

from .Browser import Browser
from .constants import VERSION

defaultConfig = "version "+VERSION+"""
# options

# how many words to flash per minute
set wpm 450

# "Home page", loaded if no url is passed on the command line
set initialUrl http://en.wikipedia.org

# show current location in the document?
set showPoint True

# show abbreviated status line and prompts?
set abbreviate False

# blank between words?
set blankBetween True

# how many links to cycle through, and how many of the most recent should be
# emphasised
set linkDisplayNum 4
set linkDisplayEmphNum 1

# how many difficult "words" (defined as character strings which are long or
# contain nonalphabetic) to show
set trickyDisplayNum 3

# how many documents to keep in the history cache
set maxCached 5

# how many blanks to insert at the end of a pause/sentence/paragraph
set paraBlankSpace 6
set sentenceBlankSpace 3
set pauseBlankSpace 1

# show context when paused?
set showContext True

# pause at regular intervals?
set blink False
# pause for blinkTime seconds out of blinkDelay seconds
set blinkTime 0.3
set blinkDelay 6
# wait until the end of a sentence before blinking?
set blinkWaitSentence False

# speed in "skim mode"
set skimWpm 1000

# keybindings

bind > changespeed 25
bind k changespeed 25
bind K changespeed 100
bind Up changespeed 100
bind < changespeed -25
bind j changespeed -25
bind J changespeed -100
bind Down changespeed -100

bind l seekSentence 1
bind L seekParagraph 1
bind ^D seekParagraph 1
bind Right seekSentence 1
bind h seekSentence -1
bind H seekParagraph -1
bind ^U seekParagraph 1
bind Left seekSentence -1
bind o seekWord 10
bind O seekWord 50
bind NPage seekWord 50
bind i seekWord -10
bind I seekWord -50
bind PPage seekWord -50
bind . seekWord 1
bind ^F seekWord 1
bind , seekWord -1
bind ^B seekWord -1
bind ] seekLink 1
bind [ seekLink -1

bind ^A seekStart
bind Home seekStart
bind ^E seekEnd
bind End seekEnd

bind g go
bind G goCurrent

bind / search 1
bind ? search -1
bind n searchAgain 1
bind N searchAgain -1

bind b history -1
bind Backspace history -1
bind u history 1

bind ^R reload

bind q quit

bind ^L refresh

bind 1 followLink 1
bind 2 followLink 2
bind 3 followLink 3
bind 4 followLink 4
bind 5 followLink 5
bind 6 followLink 6
bind 7 followLink 7
bind 8 followLink 8
bind 9 followLink 9

bind 0 count

bind m mark
bind ' goMark

bind $ skim

bind space pause

bind : command

bind F1 help


# shortcuts:
# Type "g foo" at the 'Go:' prompt to search with google for "foo".
shortcut g http://www.google.com/search?q=%1
shortcut wp http://en.wikipedia.org/wiki/%1

# Shortcuts can be recursive;
shortcut wps g site:en.wikipedia.org %1
shortcut wk wp %1
shortcut wks wps %1

# and can have multiple arguments;
shortcut gsite g site:%1 %2

# %c expands to the current url;
shortcut gs gsite %c %1

# and $HOME expands to your home directory.
shortcut rss $HOME/.rawdog/out.html
"""

class Config:
    optionsInfo = {
                # <option name> : <type>
                'wpm' : int,
                'initialUrl' : str,
                'blink' : bool,
                'blankBetween' : bool,
                'abbreviate' : bool,
                'showPoint' : bool,
                'showContext' : bool,
                'linkDisplayNum' : int,
                'linkDisplayEmphNum' : int,
                'trickyDisplayNum' : int,
                'maxCached' : int,
                'paraBlankSpace' : int,
                'sentenceBlankSpace' : int,
                'pauseBlankSpace' : int,
                'blinkTime' : float,
                'blinkDelay' : float,
                'blinkWaitSentence' : bool,
                'skimWpm' : int
            }

    # this list consists of the keys of optionsInfo in the order we want
    # --help to present them
    optionNames = [
            'wpm', 'initialUrl', 'maxCached', 'abbreviate', 'showPoint',
            'showContext',
            'linkDisplayNum', 'linkDisplayEmphNum', 'trickyDisplayNum',
            'paraBlankSpace', 'sentenceBlankSpace', 'pauseBlankSpace', 'blankBetween',
            'blink', 'blinkTime', 'blinkDelay', 'blinkWaitSentence',
            'skimWpm' ]

    def __init__(self):
        self.options = {}
        self.keyBindings = {}
        self.urlShortcuts = {}
        self.loadErrors = []

        self.load(StringIO(defaultConfig), "[Default Config]")

        filename = os.getenv('HOME')+'/.flinksrc'
        try:
            file = open(filename, 'r')
            self.load(file, filename)
        except IOError:
            # write default config
            open(filename, 'w').write(defaultConfig)

    def processArgs(self, argv):
        from getopt import gnu_getopt

        shortOptDict = { "wpm": "w", "showPoint": "p",
                "abbreviate": "a", "blink": "b" }

        longopts = ["help"]
        for optName in self.optionNames:
            if self.optionsInfo[optName] == bool:
                longopts += [optName, "%s" % optName.lower()]
                longopts += [optName, "no%s" % optName.lower()]
            else:
                longopts.append("%s=" % optName.lower())

        opts, args = gnu_getopt(argv, 'hw:pPaAbB', longopts)

        for opt, val in opts:
            if opt in ["-h", "--help"]:
                def helpStr(optName):
                    short = shortOptDict.get(optName, None)
                    type = self.optionsInfo[optName]
                    if short:
                        if type == bool:
                            shopts = "-%s/%s, " % (short, short.upper())
                        else:
                            shopts = "-%s, " % short
                    else:
                        shopts = ''
                    if type == bool:
                        lopts = "--[no]%s" % optName.lower()
                    elif type in [int,float] :
                        lopts = "--%s=N" % optName.lower()
                    elif type == str:
                        lopts = "--%s=STRING" % optName.lower()
                    return " "+shopts+lopts
                print(("Usage: " + argv[0] +
                        " [OPTION]... [INITIAL_URL]\nOptions:\n"
                        " -h, --help\n" +
                        ''.join( [helpStr(optName)
                            for optName in self.optionNames
                            if optName != "initialUrl"], '\n')))
                return 0

            for optName in self.optionNames:
                short = shortOptDict.get(optName, None)
                type = self.optionsInfo[optName]
                if opt == "--"+optName.lower() or (short and opt == "-"+short):
                    if type == bool:
                        self.options[optName] = True
                    elif type in [int,float]:
                        try:
                            self.options[optName] = type(val)
                        except ValueError:
                            print("--%s: expected %s value" % (
                                    optName.lower(), type.__name__))
                            return 1
                    elif type == str:
                        self.options[optName] = val

                elif type == bool and opt == "--no"+optName.lower() or (
                        short and opt == "-"+short.upper()):
                    self.options[optName] = False

        if args[1:]:
            self.options["initialUrl"] = ''.join(args[1:])

        return None

    def load(self, file, filename):
        lineNumber = 0

        def addError(details):
            self.loadErrors.append("While parsing %s, line %d : %s" %
                    (filename, lineNumber, details))

        for line in file:
            lineNumber += 1
            self.processCommand(line, errorHandler=addError)

    def processCommand(self, command, errorHandler=(lambda _: None)):
        words = command.split()

        if words == [] or words[0][0] == '#':
            return

        command = words[0]

        if command == "version":
            # ignored for now
            pass
        elif command == "set":
            if (len(words) < 3):
                errorHandler("usage: set OPTION VALUE")
                return

            for option in list(self.optionsInfo.keys()):
                if words[1].lower() == option.lower():
                    optionType = self.optionsInfo[option]
                    if optionType in [int,float]:
                        try:
                            self.options[option] = optionType(words[2])
                        except ValueError:
                            errorHandler("expected %s value", optionType.__name__)
                    elif optionType == bool:
                        self.options[option] = (words[2].lower() == 'true')
                    elif optionType == str:
                        self.options[option] = words[2]
                    else:
                        errorHandler("*BUG*: unknown type for option value")
                    return
            errorHandler("Unknown option: %s" % words[1])

        elif command == "bind":
            if (len(words) < 2):
                errorHandler("usage: bind KEY [FUNCTION [ARGS...]]")
                return

            key = None

            keyName = words[1]

            if len(keyName) == 1:
                # A
                key = ord(keyName)
            elif len(keyName) == 2 and keyName[0] == '^':
                # ^A
                key = ascii.ctrl(ord(keyName[1]))
            elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'C':
                # C-A
                key = ascii.ctrl(ord(keyName[2]))
            elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'M':
                # M-A
                key = ascii.alt(ord(keyName[2]))
            elif keyName.lower() == "space":
                # space (special case)
                key = ord(' ')
            elif keyName.lower() == "esc" or keyName.lower() == "escape":
                # escape (special case)
                key = 27
            else:
                # down
                try:
                    key = getattr(curses, 'KEY_'+keyName.upper())
                except AttributeError:
                    pass

            if key is None:
                errorHandler("bad key description \"%s\"" % (words[1]))
                return

            if len(words) == 2:
                # clear binding
                if key in self.keyBindings:
                    del self.keyBindings[key]
            else:
                # add binding
                method = None
                for possMethod in list(Browser.bindableMethods.keys()):
                    if words[2].lower() == possMethod.lower():
                        method = possMethod
                if method is None:
                    errorHandler("unknown function \"%s\"" % words[2])
                    return

                minArgs = Browser.bindableMethods[method][0]
                maxArgs = Browser.bindableMethods[method][1]

                if len(words) - 3 < minArgs or len(words) - 3 > maxArgs:
                    errorHandler("\"%s\" requires %d-%d arguments" %
                            (method, minArgs, maxArgs))
                    return

                args = []
                for arg in words[3:]:
                    try:
                        args.append(int(arg))
                    except ValueError:
                        errorHandler("expected integer argument")
                        return

                self.keyBindings[key] = [method, args]

        elif command=="shortcut":
            if (len(words) < 3):
                errorHandler("usage: shortcut KEYWORD URL")
                return
            command = ''.join(words[2:])
            i=1
            while re.search('%%%d' % i, command):
                i+=1
            numArgs = i-1
            self.urlShortcuts[words[1]] = [numArgs, command]

        else:
            errorHandler("unknown command \"%s\"" % words[0])
