# 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 json
from os.path import dirname, isdir

import click

from platformio import app, exception, util
from platformio.commands.boards import print_boards
from platformio.managers.platform import PlatformFactory, PlatformManager


@click.group(short_help="Platform Manager")
def cli():
    pass


def _print_platforms(platforms):
    for platform in platforms:
        click.echo("{name} ~ {title}".format(
            name=click.style(platform['name'], fg="cyan"),
            title=platform['title']))
        click.echo("=" * (3 + len(platform['name'] + platform['title'])))
        click.echo(platform['description'])
        click.echo()
        if "homepage" in platform:
            click.echo("Home: %s" % platform['homepage'])
        if "frameworks" in platform and platform['frameworks']:
            click.echo("Frameworks: %s" % ", ".join(platform['frameworks']))
        if "packages" in platform:
            click.echo("Packages: %s" % ", ".join(platform['packages']))
        if "version" in platform:
            click.echo("Version: " + platform['version'])
        click.echo()


def _get_registry_platforms():
    platforms = util.get_api_result("/platforms", cache_valid="30d")
    pm = PlatformManager()
    for platform in platforms or []:
        platform['versions'] = pm.get_all_repo_versions(platform['name'])
    return platforms


def _original_version(version):
    if version.count(".") != 2:
        return None
    _, y = version.split(".")[:2]
    if int(y) < 100:
        return None
    if len(y) % 2 != 0:
        y = "0" + y
    parts = [str(int(y[i * 2:i * 2 + 2])) for i in range(len(y) / 2)]
    return ".".join(parts)


def _get_platform_data(*args, **kwargs):
    try:
        return _get_installed_platform_data(*args, **kwargs)
    except exception.UnknownPlatform:
        return _get_registry_platform_data(*args, **kwargs)


def _get_installed_platform_data(platform,
                                 with_boards=True,
                                 expose_packages=True):
    p = PlatformFactory.newPlatform(platform)
    data = dict(
        name=p.name,
        title=p.title,
        description=p.description,
        version=p.version,
        homepage=p.homepage,
        repository=p.repository_url,
        url=p.vendor_url,
        license=p.license,
        forDesktop=not p.is_embedded(),
        frameworks=sorted(p.frameworks.keys() if p.frameworks else []),
        packages=p.packages.keys() if p.packages else [])

    # if dump to API
    # del data['version']
    # return data

    # overwrite VCS version and add extra fields
    manifest = PlatformManager().load_manifest(dirname(p.manifest_path))
    assert manifest
    for key in manifest:
        if key == "version" or key.startswith("__"):
            data[key] = manifest[key]

    if with_boards:
        data['boards'] = [c.get_brief_data() for c in p.get_boards().values()]

    if not data['packages'] or not expose_packages:
        return data

    data['packages'] = []
    installed_pkgs = p.get_installed_packages()
    for name, opts in p.packages.items():
        item = dict(
            name=name,
            type=p.get_package_type(name),
            requirements=opts.get("version"),
            optional=opts.get("optional") is True)
        if name in installed_pkgs:
            for key, value in installed_pkgs[name].items():
                if key not in ("url", "version", "description"):
                    continue
                item[key] = value
                if key == "version":
                    item["originalVersion"] = _original_version(value)
        data['packages'].append(item)

    return data


def _get_registry_platform_data(  # pylint: disable=unused-argument
        platform,
        with_boards=True,
        expose_packages=True):
    _data = None
    for p in _get_registry_platforms():
        if p['name'] == platform:
            _data = p
            break

    if not _data:
        return None

    data = dict(
        name=_data['name'],
        title=_data['title'],
        description=_data['description'],
        homepage=_data['homepage'],
        repository=_data['repository'],
        url=_data['url'],
        license=_data['license'],
        forDesktop=_data['forDesktop'],
        frameworks=_data['frameworks'],
        packages=_data['packages'],
        versions=_data['versions'])

    if with_boards:
        data['boards'] = [
            board for board in PlatformManager().get_registered_boards()
            if board['platform'] == _data['name']
        ]

    return data


@cli.command("search", short_help="Search for development platform")
@click.argument("query", required=False)
@click.option("--json-output", is_flag=True)
def platform_search(query, json_output):
    platforms = []
    for platform in _get_registry_platforms():
        if query == "all":
            query = ""
        search_data = json.dumps(platform)
        if query and query.lower() not in search_data.lower():
            continue
        platforms.append(
            _get_registry_platform_data(
                platform['name'], with_boards=False, expose_packages=False))

    if json_output:
        click.echo(json.dumps(platforms))
    else:
        _print_platforms(platforms)


@cli.command("frameworks", short_help="List supported frameworks, SDKs")
@click.argument("query", required=False)
@click.option("--json-output", is_flag=True)
def platform_frameworks(query, json_output):
    frameworks = []
    for framework in util.get_api_result("/frameworks", cache_valid="30d"):
        if query == "all":
            query = ""
        search_data = json.dumps(framework)
        if query and query.lower() not in search_data.lower():
            continue
        framework['homepage'] = (
            "http://platformio.org/frameworks/" + framework['name'])
        framework['platforms'] = [
            platform['name'] for platform in _get_registry_platforms()
            if framework['name'] in platform['frameworks']
        ]
        frameworks.append(framework)

    frameworks = sorted(frameworks, key=lambda manifest: manifest['name'])
    if json_output:
        click.echo(json.dumps(frameworks))
    else:
        _print_platforms(frameworks)


@cli.command("list", short_help="List installed development platforms")
@click.option("--json-output", is_flag=True)
def platform_list(json_output):
    platforms = []
    pm = PlatformManager()
    for manifest in pm.get_installed():
        platforms.append(
            _get_installed_platform_data(
                manifest['__pkg_dir'],
                with_boards=False,
                expose_packages=False))

    platforms = sorted(platforms, key=lambda manifest: manifest['name'])
    if json_output:
        click.echo(json.dumps(platforms))
    else:
        _print_platforms(platforms)


@cli.command("show", short_help="Show details about development platform")
@click.argument("platform")
@click.option("--json-output", is_flag=True)
def platform_show(platform, json_output):  # pylint: disable=too-many-branches
    data = _get_platform_data(platform)
    if not data:
        raise exception.UnknownPlatform(platform)
    if json_output:
        return click.echo(json.dumps(data))

    click.echo("{name} ~ {title}".format(
        name=click.style(data['name'], fg="cyan"), title=data['title']))
    click.echo("=" * (3 + len(data['name'] + data['title'])))
    click.echo(data['description'])
    click.echo()
    if "version" in data:
        click.echo("Version: %s" % data['version'])
    if data['homepage']:
        click.echo("Home: %s" % data['homepage'])
    if data['repository']:
        click.echo("Repository: %s" % data['repository'])
    if data['url']:
        click.echo("Vendor: %s" % data['url'])
    if data['license']:
        click.echo("License: %s" % data['license'])
    if data['frameworks']:
        click.echo("Frameworks: %s" % ", ".join(data['frameworks']))

    if not data['packages']:
        return

    if not isinstance(data['packages'][0], dict):
        click.echo("Packages: %s" % ", ".join(data['packages']))
    else:
        click.echo()
        click.secho("Packages", bold=True)
        click.echo("--------")
        for item in data['packages']:
            click.echo()
            click.echo("Package %s" % click.style(item['name'], fg="yellow"))
            click.echo("-" * (8 + len(item['name'])))
            if item['type']:
                click.echo("Type: %s" % item['type'])
            click.echo("Requirements: %s" % item['requirements'])
            click.echo("Installed: %s" %
                       ("Yes" if item.get("version") else "No (optional)"))
            if "version" in item:
                click.echo("Version: %s" % item['version'])
            if "originalVersion" in item:
                click.echo("Original version: %s" % item['originalVersion'])
            if "description" in item:
                click.echo("Description: %s" % item['description'])

    if data['boards']:
        click.echo()
        click.secho("Boards", bold=True)
        click.echo("------")
        print_boards(data['boards'])


@cli.command("install", short_help="Install new development platform")
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
@click.option("--with-package", multiple=True)
@click.option("--without-package", multiple=True)
@click.option("--skip-default-package", is_flag=True)
def platform_install(platforms, with_package, without_package,
                     skip_default_package):
    pm = PlatformManager()
    for platform in platforms:
        if pm.install(
                name=platform,
                with_packages=with_package,
                without_packages=without_package,
                skip_default_package=skip_default_package):
            click.secho(
                "The platform '%s' has been successfully installed!\n"
                "The rest of packages will be installed automatically "
                "depending on your build environment." % platform,
                fg="green")


@cli.command("uninstall", short_help="Uninstall development platform")
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
def platform_uninstall(platforms):
    pm = PlatformManager()
    for platform in platforms:
        if pm.uninstall(platform):
            click.secho(
                "The platform '%s' has been successfully "
                "uninstalled!" % platform,
                fg="green")


@cli.command("update", short_help="Update installed development platforms")
@click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]")
@click.option(
    "-p",
    "--only-packages",
    is_flag=True,
    help="Update only the platform packages")
@click.option(
    "-c",
    "--only-check",
    is_flag=True,
    help="Do not update, only check for a new version")
@click.option("--json-output", is_flag=True)
def platform_update(platforms, only_packages, only_check, json_output):
    pm = PlatformManager()
    pkg_dir_to_name = {}
    if not platforms:
        platforms = []
        for manifest in pm.get_installed():
            platforms.append(manifest['__pkg_dir'])
            pkg_dir_to_name[manifest['__pkg_dir']] = manifest.get(
                "title", manifest['name'])

    if only_check and json_output:
        result = []
        for platform in platforms:
            pkg_dir = platform if isdir(platform) else None
            requirements = None
            url = None
            if not pkg_dir:
                name, requirements, url = pm.parse_pkg_input(platform)
                pkg_dir = pm.get_package_dir(name, requirements, url)
            if not pkg_dir:
                continue
            latest = pm.outdated(pkg_dir, requirements)
            if (not latest and not PlatformFactory.newPlatform(pkg_dir)
                    .are_outdated_packages()):
                continue
            data = _get_installed_platform_data(
                pkg_dir, with_boards=False, expose_packages=False)
            if latest:
                data['versionLatest'] = latest
            result.append(data)
        return click.echo(json.dumps(result))
    else:
        # cleanup cached board and platform lists
        app.clean_cache()
        for platform in platforms:
            click.echo("Platform %s" % click.style(
                pkg_dir_to_name.get(platform, platform), fg="cyan"))
            click.echo("--------")
            pm.update(
                platform, only_packages=only_packages, only_check=only_check)
            click.echo()
