# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
from urllib.parse import urlparse

import click
import uvicorn
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.responses import PlainTextResponse
from starlette.routing import Mount, Route, WebSocketRoute
from starlette.staticfiles import StaticFiles
from starlette.status import HTTP_403_FORBIDDEN

from platformio.commands.home.rpc.handlers.account import AccountRPC
from platformio.commands.home.rpc.handlers.app import AppRPC
from platformio.commands.home.rpc.handlers.ide import IDERPC
from platformio.commands.home.rpc.handlers.misc import MiscRPC
from platformio.commands.home.rpc.handlers.os import OSRPC
from platformio.commands.home.rpc.handlers.piocore import PIOCoreRPC
from platformio.commands.home.rpc.handlers.project import ProjectRPC
from platformio.commands.home.rpc.server import WebSocketJSONRPCServerFactory
from platformio.compat import aio_get_running_loop
from platformio.exception import PlatformioException
from platformio.package.manager.core import get_core_package_dir
from platformio.proc import force_exit


class ShutdownMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        if scope["type"] == "http" and b"__shutdown__" in scope.get("query_string", {}):
            await shutdown_server()
        await self.app(scope, receive, send)


async def shutdown_server(_=None):
    aio_get_running_loop().call_later(0.5, force_exit)
    return PlainTextResponse("Server has been shutdown!")


async def protected_page(_):
    return PlainTextResponse(
        "Protected PlatformIO Home session", status_code=HTTP_403_FORBIDDEN
    )


def run_server(host, port, no_open, shutdown_timeout, home_url):
    contrib_dir = get_core_package_dir("contrib-piohome")
    if not os.path.isdir(contrib_dir):
        raise PlatformioException("Invalid path to PIO Home Contrib")

    ws_rpc_factory = WebSocketJSONRPCServerFactory(shutdown_timeout)
    ws_rpc_factory.addObjectHandler(AccountRPC(), namespace="account")
    ws_rpc_factory.addObjectHandler(AppRPC(), namespace="app")
    ws_rpc_factory.addObjectHandler(IDERPC(), namespace="ide")
    ws_rpc_factory.addObjectHandler(MiscRPC(), namespace="misc")
    ws_rpc_factory.addObjectHandler(OSRPC(), namespace="os")
    ws_rpc_factory.addObjectHandler(PIOCoreRPC(), namespace="core")
    ws_rpc_factory.addObjectHandler(ProjectRPC(), namespace="project")

    path = urlparse(home_url).path
    routes = [
        WebSocketRoute(path + "wsrpc", ws_rpc_factory, name="wsrpc"),
        Route(path + "__shutdown__", shutdown_server, methods=["POST"]),
        Mount(path, StaticFiles(directory=contrib_dir, html=True), name="static"),
    ]
    if path != "/":
        routes.append(Route("/", protected_page))

    uvicorn.run(
        Starlette(
            middleware=[Middleware(ShutdownMiddleware)],
            routes=routes,
            on_startup=[
                lambda: click.echo(
                    "PIO Home has been started. Press Ctrl+C to shutdown."
                ),
                lambda: None if no_open else click.launch(home_url),
            ],
        ),
        host=host,
        port=port,
        log_level="warning",
    )

[((53, 11, 53, 57), 'starlette.responses.PlainTextResponse', 'PlainTextResponse', ({(53, 29, 53, 56): '"""Server has been shutdown!"""'}, {}), "('Server has been shutdown!')", False, 'from starlette.responses import PlainTextResponse\n'), ((57, 11, 59, 5), 'starlette.responses.PlainTextResponse', 'PlainTextResponse', (), '', False, 'from starlette.responses import PlainTextResponse\n'), ((63, 18, 63, 57), 'platformio.package.manager.core.get_core_package_dir', 'get_core_package_dir', ({(63, 39, 63, 56): '"""contrib-piohome"""'}, {}), "('contrib-piohome')", False, 'from platformio.package.manager.core import get_core_package_dir\n'), ((67, 21, 67, 68), 'platformio.commands.home.rpc.server.WebSocketJSONRPCServerFactory', 'WebSocketJSONRPCServerFactory', ({(67, 51, 67, 67): 'shutdown_timeout'}, {}), '(shutdown_timeout)', False, 'from platformio.commands.home.rpc.server import WebSocketJSONRPCServerFactory\n'), ((64, 11, 64, 37), 'os.path.isdir', 'os.path.isdir', ({(64, 25, 64, 36): 'contrib_dir'}, {}), '(contrib_dir)', False, 'import os\n'), ((65, 14, 65, 69), 'platformio.exception.PlatformioException', 'PlatformioException', ({(65, 34, 65, 68): '"""Invalid path to PIO Home Contrib"""'}, {}), "('Invalid path to PIO Home Contrib')", False, 'from platformio.exception import PlatformioException\n'), ((68, 36, 68, 48), 'platformio.commands.home.rpc.handlers.account.AccountRPC', 'AccountRPC', ({}, {}), '()', False, 'from platformio.commands.home.rpc.handlers.account import AccountRPC\n'), ((69, 36, 69, 44), 'platformio.commands.home.rpc.handlers.app.AppRPC', 'AppRPC', ({}, {}), '()', False, 'from platformio.commands.home.rpc.handlers.app import AppRPC\n'), ((70, 36, 70, 44), 'platformio.commands.home.rpc.handlers.ide.IDERPC', 'IDERPC', ({}, {}), '()', False, 'from platformio.commands.home.rpc.handlers.ide import IDERPC\n'), ((71, 36, 71, 45), 'platformio.commands.home.rpc.handlers.misc.MiscRPC', 'MiscRPC', ({}, {}), '()', False, 'from platformio.commands.home.rpc.handlers.misc import MiscRPC\n'), ((72, 36, 72, 43), 'platformio.commands.home.rpc.handlers.os.OSRPC', 'OSRPC', ({}, {}), '()', False, 'from platformio.commands.home.rpc.handlers.os import OSRPC\n'), ((73, 36, 73, 48), 'platformio.commands.home.rpc.handlers.piocore.PIOCoreRPC', 'PIOCoreRPC', ({}, {}), '()', False, 'from platformio.commands.home.rpc.handlers.piocore import PIOCoreRPC\n'), ((74, 36, 74, 48), 'platformio.commands.home.rpc.handlers.project.ProjectRPC', 'ProjectRPC', ({}, {}), '()', False, 'from platformio.commands.home.rpc.handlers.project import ProjectRPC\n'), ((76, 11, 76, 29), 'urllib.parse.urlparse', 'urlparse', ({(76, 20, 76, 28): 'home_url'}, {}), '(home_url)', False, 'from urllib.parse import urlparse\n'), ((78, 8, 78, 68), 'starlette.routing.WebSocketRoute', 'WebSocketRoute', (), '', False, 'from starlette.routing import Mount, Route, WebSocketRoute\n'), ((79, 8, 79, 71), 'starlette.routing.Route', 'Route', (), '', False, 'from starlette.routing import Mount, Route, WebSocketRoute\n'), ((52, 4, 52, 26), 'platformio.compat.aio_get_running_loop', 'aio_get_running_loop', ({}, {}), '()', False, 'from platformio.compat import aio_get_running_loop\n'), ((80, 20, 80, 65), 'starlette.staticfiles.StaticFiles', 'StaticFiles', (), '', False, 'from starlette.staticfiles import StaticFiles\n'), ((83, 22, 83, 48), 'starlette.routing.Route', 'Route', ({(83, 28, 83, 31): '"""/"""', (83, 33, 83, 47): 'protected_page'}, {}), "('/', protected_page)", False, 'from starlette.routing import Mount, Route, WebSocketRoute\n'), ((87, 24, 87, 54), 'starlette.middleware.Middleware', 'Middleware', ({(87, 35, 87, 53): 'ShutdownMiddleware'}, {}), '(ShutdownMiddleware)', False, 'from starlette.middleware import Middleware\n'), ((90, 24, 92, 17), 'click.echo', 'click.echo', ({(91, 20, 91, 74): '"""PIO Home has been started. Press Ctrl+C to shutdown."""'}, {}), "('PIO Home has been started. Press Ctrl+C to shutdown.')", False, 'import click\n'), ((93, 45, 93, 67), 'click.launch', 'click.launch', ({(93, 58, 93, 66): 'home_url'}, {}), '(home_url)', False, 'import click\n')]