# Copyright (C) 2017, 2019 nickolas360 <contact@nickolas360.com>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha.  If not, see <http://www.gnu.org/licenses/>.

from .errors import UserError, UserExit
from .librecaptcha import get_token, __version__
from .user_agents import random_user_agent
import os
import re
import sys


def get_cmd():
    if not sys.argv:
        return "librecaptcha"
    if sys.argv[0].startswith("./"):
        return sys.argv[0]
    return os.path.basename(sys.argv[0])


CMD = get_cmd()
USAGE = """\
Usage:
  {0} [options] [--] <api-key> <site-url> [<user-agent>]
  {0} -h | --help | --version

Arguments:
     <api-key>  The reCAPTCHA API key to use. This is usually the value of the
                "data-sitekey" HTML attribute.

    <site-url>  The URL of the site that contains the reCAPTCHA challenge.
                Should start with http:// or https://. Everything after the
                hostname is optional. For example: https://example.com

  <user-agent>  A user-agent string. The client that will use the obtained
                reCAPTCHA token should have this user-agent string. If not
                provided, a random user-agent string will be chosen and shown.

Options:
   -g --gui  Use the GTK 3 GUI (as opposed to the CLI).
    --debug  Show debugging information while running.
  -h --help  Show this help message.
  --version  Show the program version.
""".format(CMD)


def usage(file=sys.stdout):
    print(USAGE, end="", file=file)


def usage_error(exit=True):
    usage(sys.stderr)
    if exit:
        sys.exit(1)


class ParsedArgs:
    def __init__(self):
        self.parse_error = None
        self.api_key = None
        self.site_url = None
        self.user_agent = None
        self.gui = False
        self.debug = False
        self.help = False
        self.version = False


class ArgParser:
    def __init__(self, args):
        self.args = args
        self.index = 0
        self.positional_index = 0
        self.parsed = ParsedArgs()
        self.options_done = False
        self.end_early = False

    @property
    def arg(self):
        try:
            return self.args[self.index]
        except IndexError:
            return None

    @property
    def done(self):
        return self.end_early or self.index >= len(self.args)

    def advance(self):
        self.index += 1

    def error(self, message):
        self.parsed.parse_error = message
        self.end_early = True

    def parse_long_option(self, arg):
        body = arg[len("--"):]
        if body == "debug":
            self.parsed.debug = True
            return
        if body == "help":
            self.parsed.help = True
            self.end_early = True
            return
        if body == "version":
            self.parsed.version = True
            self.end_early = True
            return
        if body == "gui":
            self.parsed.gui = True
            return
        self.error("Unrecognized option: {}".format(arg))

    def parse_short_option_char(self, char):
        if char == "h":
            self.parsed.help = True
            self.end_early = True
            return
        if char == "g":
            self.parsed.gui = True
            return
        self.error("Unrecognized option: -{}".format(char))

    def parse_short_option(self, arg):
        body = arg[len("-"):]
        for char in body:
            self.parse_short_option_char(char)

    def try_parse_option(self):
        arg = self.arg
        if arg == "--":
            self.options_done = True
            return True
        if re.match(r"--[^-]", arg):
            self.parse_long_option(arg)
            return True
        if re.match(r"-[^-]", arg):
            self.parse_short_option(arg)
            return True
        return False

    def parse_positional(self):
        arg = self.arg
        if self.positional_index == 0:
            self.parsed.api_key = arg
            return
        if self.positional_index == 1:
            self.parsed.site_url = arg
            return
        if self.positional_index == 2:
            self.parsed.user_agent = arg
            return
        self.error("Unexpected positional argument: {}".format(arg))

    def parse_single(self):
        if not self.options_done and self.try_parse_option():
            return
        self.parse_positional()
        self.positional_index += 1

    def handle_end(self):
        if self.end_early:
            return
        if self.positional_index < 1:
            self.error("Missing positional argument: <api-key>")
            return
        if self.positional_index < 2:
            self.error("Missing positional argument: <site-url>")
            return

    def parse(self):
        while not self.done:
            self.parse_single()
            self.advance()
        self.handle_end()
        return self.parsed


GOT_TOKEN_MSG = """\
Received token. This token should usually be submitted with the form as the
value of the "g-recaptcha-response" field.
"""


def run(args: ParsedArgs):
    random_ua = False
    user_agent = args.user_agent
    if args.user_agent is None:
        random_ua = True
        user_agent = random_user_agent()
    if args.debug:
        print("User-agent string: {}".format(user_agent), file=sys.stderr)

    uvtoken = get_token(
        args.api_key, args.site_url, user_agent,
        gui=args.gui, debug=args.debug,
    )
    print(GOT_TOKEN_MSG)
    if random_ua:
        print("Note: The following user-agent string was used:")
        print(user_agent)
        print()
    print("Token:")
    print(uvtoken)


UNEXPECTED_ERR_MSG = """\
An unexpected error occurred. The exception traceback is shown below:
"""


def run_or_exit(args: ParsedArgs):
    if args.debug:
        return run(args)
    try:
        return run(args)
    except UserExit:
        sys.exit(2)
    except UserError as e:
        print(e.message, file=sys.stderr)
        sys.exit(1)
    except KeyboardInterrupt:
        print(file=sys.stderr)
        sys.exit(2)
    except Exception:
        print(UNEXPECTED_ERR_MSG, file=sys.stderr)
        raise


def main():
    args = sys.argv[1:]
    parsed = ArgParser(args).parse()
    error = parsed.parse_error

    if error is not None:
        print(error, file=sys.stderr)
        print("For usage information, run: {} --help".format(CMD),
              file=sys.stderr)
        sys.exit(1)

    if parsed.help:
        usage()
        return

    if parsed.version:
        print(__version__)
        return
    run_or_exit(parsed)


if __name__ == "__main__":
    main()
