#!/usr/bin/env python3

import argparse
import json
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn

import vyper
from vyper.codegen import ir_node
from vyper.evm.opcodes import DEFAULT_EVM_VERSION
from vyper.exceptions import VyperException


def _parse_cli_args():
    return _parse_args(sys.argv[1:])


def _parse_args(argv):
    parser = argparse.ArgumentParser(description="Serve Vyper compiler as an HTTP Service")
    parser.add_argument(
        "--version", action="version", version=f"{vyper.__version__}+commit{vyper.__commit__}"
    )
    parser.add_argument(
        "-b",
        help="Address to bind JSON server on, default: localhost:8000",
        default="localhost:8000",
        dest="bind_address",
    )

    args = parser.parse_args(argv)

    if ":" in args.bind_address:
        ir_node.VYPER_COLOR_OUTPUT = False
        runserver(*args.bind_address.split(":"))
    else:
        print('Provide bind address in "{address}:{port}" format')


class VyperRequestHandler(BaseHTTPRequestHandler):
    def send_404(self):
        self.send_response(404)
        self.end_headers()
        return

    def send_cors_all(self):
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-type")

    def do_OPTIONS(self):
        self.send_response(200)
        self.send_cors_all()
        self.end_headers()

    def do_GET(self):
        if self.path == "/":
            self.send_response(200)
            self.send_cors_all()
            self.end_headers()
            self.wfile.write(f"Vyper Compiler. Version: {vyper.__version__}\n".encode())
        else:
            self.send_404()

        return

    def do_POST(self):
        if self.path == "/compile":
            content_len = int(self.headers.get("content-length"))
            post_body = self.rfile.read(content_len)
            data = json.loads(post_body)

            response, status_code = self._compile(data)

            self.send_response(status_code)
            self.send_header("Content-type", "application/json")
            self.send_cors_all()
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())

        else:
            self.send_404()

        return

    def _compile(self, data):
        code = data.get("code")
        if not code:
            return {"status": "failed", "message": 'No "code" key supplied'}, 400
        if not isinstance(code, str):
            return {"status": "failed", "message": '"code" must be a non-empty string'}, 400

        try:
            code = data["code"]
            out_dict = vyper.compile_codes(
                {"": code},
                list(vyper.compiler.OUTPUT_FORMATS.keys()),
                evm_version=data.get("evm_version", DEFAULT_EVM_VERSION),
            )[""]
            out_dict["ir"] = str(out_dict["ir"])
            out_dict["ir_runtime"] = str(out_dict["ir_runtime"])
        except VyperException as e:
            return (
                {"status": "failed", "message": str(e), "column": e.col_offset, "line": e.lineno},
                400,
            )
        except SyntaxError as e:
            return (
                {"status": "failed", "message": str(e), "column": e.offset, "line": e.lineno},
                400,
            )

        out_dict.update({"status": "success"})

        return out_dict, 200


class VyperHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

    pass


def runserver(host="", port=8000):
    server_address = (host, int(port))
    httpd = VyperHTTPServer(server_address, VyperRequestHandler)
    print(f"Listening on http://{host}:{port}")
    httpd.serve_forever()
