#!/usr/bin/env python3
import requests
import typing

try:
    import colorama
    colorama.init()
    COLOR_RED=colorama.Fore.RED
    COLOR_RESET=colorama.Style.RESET_ALL
except ImportError:
    COLOR_RED=''
    COLOR_RESET=''


def dbprint(arg: typing.Any) -> typing.Any:
    print(f'{COLOR_RED}debug>{COLOR_RESET}', repr(arg))
    return arg


class PaperMC:
    '''Class for accessing PaperMC API endpoint'''
    version = 'v2'
    endpoint = f'https://papermc.io/api/{version}'

    projects_controller = f'{endpoint}/projects'
    url=projects_controller

    @staticmethod
    def _get(url: str) -> typing.Any:
        r=requests.get(url)
        return r.json()

    @classmethod
    def get_projects(cls) -> typing.List:
        return [Project(x) \
                for x in cls._get(cls.url)["projects"]]

class PaperMC_Async(PaperMC):
    @staticmethod
    async def _get(url: str) -> typing.Any:
        pass

class Project(PaperMC):
    '''An object representing a project within PaperMC'''
    def __init__(self, project: str):
        self.project=str(project)
        self.project_controller=f'{self.projects_controller}/{self.project}'

    def __repr__(self):
        return str(self.project)

    def get_project(self):
        return Project(self.project)

    def get_version_groups(self) -> typing.List:
        return [VersionGroup(self.project, x) \
                for x in self._get(self.project_controller)["version_groups"]]

    def get_versions(self) -> typing.List:
        return [Version(self.project, x) \
                for x in self._get(self.project_controller)["versions"]]


class _VersionParent(Project):
    def __init__(self, project: str, version: str):
        super().__init__(project)
        self.version=str(version)
        # list of builds, info
        self.version_controller=\
                f'{self.project_controller}/versions/{self.version}'
        # info about version family
        self.version_family_controller=\
                f'{self.project_controller}/version_group/{self.version}'
        # builds in version family
        self.version_family_builds_controller=\
                f'{self.version_family_controller}/builds'

    def __repr__(self):
        return str(self.version)

    def get_version(self):
        return Version(self.project, self.version)

    def get_version_group(self):
        return VersionGroup(self.project, self.version)


class VersionGroup(_VersionParent):
    '''
    An object representing a group of versions of a PaperMC project

    For Paper, this is usually a group of all versions of a full Minecraft release, i.e., every version within 1.18
    '''
    def get_info(self) -> typing.Any:
        return self._get(self.version_family_controller)

    def get_versions(self) -> typing.List:
        return [Version(self.project, x) \
                for x in self._get(self.version_family_controller)["versions"]]

    def get_builds(self) -> typing.List:
        return [Build(self.project, x["version"], x["build"]) \
                for x in self._get(self.version_family_builds_controller)["builds"]]


class Version(_VersionParent):
    '''
    An object representing a single version of a PaperMC project

    For Paper, this is a single Minecraft version; i.e., 1.18.1
    '''
    def get_builds(self) -> typing.List:
        return [Build(self.project, self.version, x) \
                for x in self._get(self.version_controller)["builds"]]


class Build(_VersionParent):
    '''
    An individual build of a single version of a PaperMC project
    '''
    def __init__(self, project: str, version: str, build: int=None,
            build_data: typing.Any=None):
        super().__init__(project, version)
        if build is not None:
            self.build=int(build)
        elif build_data is not None:
            pass
        else:
            raise TypeError('either build or build_data should be defined.')
        self.version_build_controller=\
                f'{self.version_controller}/builds/{self.build}'

    def __repr__(self):
        return str(self.build)

    def get_build(self):
        return Build(self.project, self.version, self.build)

    def get_info(self):
        return self._get(self.version_build_controller)

    def get_downloads(self):
        info=self.get_info()
        return [Download(self.project, self.version, self.build, k, v)
                for (k, v) in info["downloads"].items()]

    def get_download_by_name(self, name: str):
        return next(x for x in self.get_downloads() if x.download_name==name)


class Download(Build):
    '''
    An artifact from a build of a PaperMC project
    '''
    def __init__(self, project: str, version: str, build: int, download_name: str, download: dict):
        super().__init__(project, version, build)
        self.download=download # dictionary including filename and sha256
        self.download_name=download_name # name for artifact
        self.name=download["name"] # filename for artifact
        self.sha256=download["sha256"] # sha256 of file
        self.download_controller=\
                f'{self.version_build_controller}/downloads/{self.name}' # Download URL

    def __repr__(self):
        return str(f'{self.download_name}: {self.download_controller}')

    def __str__(self):
        return str(self.download_controller)

get_projects=PaperMC.get_projects


if __name__ == '__main__':
    import argparse
    import os

    def abort(message: str, status: int=1):
        print(message)
        exit(status)

    def get_best_item(item):
        if item != 'auto':
            return item
        else:
            if project is not None:
                if version_like is not None:
                    if build is not None:
                        return 'downloads'
                    else:
                        return 'builds'
                else:
                    return 'version-groups'
            else:
                return 'projects'


    # Initialize Variables
    project=None
    version=None
    version_group=None
    version_like=None
    build=None
    download=None

    def list_items(items_to_list: str):
        match get_best_item(items_to_list):
            case 'projects':
                for i in PaperMC.get_projects():
                    print(i)

            case 'versions':
                if project is not None:
                    for i in project.get_versions():
                        print(i)

            case 'version-groups':
                if project is not None:
                    for i in project.get_version_groups():
                        print(i)

            case 'builds':
                if version_like is not None:
                    for i in version_like.get_builds():
                        info=i.get_info()
                        print(i, ':', info)

            case 'downloads':
                if build is not None:
                    for i in build.get_downloads():
                        print(repr(i))




    p = argparse.ArgumentParser(description=
            f"This is a script to download files from PaperMC projects.{os.linesep}Please note that automatic updating is NOT SUPPORTED. This wrapper exists mainly for convenience to avoid manually having to wget the update. Updates should still be manually reviewed to ensure they are stable, and backups should *always* be taken.")
    p.add_argument('--project', '-p', nargs='?', default='paper', help='project to act on (default: %(default)s)')
    p_version=p.add_mutually_exclusive_group(required=False)
    p_version.add_argument('--version', '-v', nargs='?', help='version number or "latest"')
    p_version.add_argument('--version-group', '-g', nargs='?', help='version group number or "latest"')
    p.add_argument('--build', '-b', nargs='?', help='build number or "latest"; omit to list all builds')
    p.add_argument('--download', '-d', nargs='?', help='download name (default: %(default)s)', default='application')
    p.add_argument('--list', '-l', nargs='?', const='auto',
            choices=['auto', 'projects', 'versions', 'version-groups', 'builds', 'downloads'],
            help='list items; no value or \'%(const)s\' lists the most fitting items for the supplied information')

    args=p.parse_args()
    dbprint(args)

    project=Project(args.project)

    # Set version object
    if args.version is not None:
        if str(args.version).lower() == 'latest':
            version=project.get_versions()[-1]
        else:
            # version=next(x for x in project.get_versions() if str(x) == args.version)
            version=Version(str(project), args.version)
        version_like=version

    # Set version group object
    if args.version_group is not None:
        if str(args.version_group).lower() == 'latest':
            version_group=project.get_version_groups()[-1]
        else:
            version_group=VersionGroup(str(project), args.version_group)
        version_like=version_group

    # Set build object
    if None not in (version_like, args.build):
        assert version_like is not None
        if str(args.build).lower() == 'latest':
            build=version_like.get_builds()[-1]
        else:
            build=Build(str(project), str(version_like), args.build)

    # Set build 
    if None not in (version_like, build, args.download):
        assert build is not None
        download=build.get_download_by_name(args.download)

    if args.list is not None:
        list_items(args.list)

    print(project)
    print(version_like)
    print(build)
    print(download)


    def debug():
        print(PaperMC.endpoint)
        print(repr(PaperMC.get_projects()))
        p=Project('paper')
        vg=p.get_version_groups()
        v=p.get_versions()
        print(vg)
        print(v)
        vv=VersionGroup('paper', '1.18').get_versions()[-1].get_builds()
        print(vv)
        latestbuild=Project('paper').get_version_groups()[-1].get_builds()[-1]
        print(latestbuild)
        print(latestbuild.get_download_by_name('application'))