from __future__ import unicode_literals

import re
import sys
from furl import furl
import urllib.parse
from operator import itemgetter
from html.parser import HTMLParser
from typing import Text

import attr
import requests

import six
from .requirements import SimpleSubstitution, FatalSpecsResolutionError, SimpleVersion

OS_TO_WHEEL_NAME = {"linux": "linux_x86_64", "windows": "win_amd64"}


def os_to_wheel_name(x):
    return OS_TO_WHEEL_NAME[x]


def fix_version(version):
    def replace(nums, prerelease):
        if prerelease:
            return "{}-{}".format(nums, prerelease)
        return nums

    return re.sub(
        r"(\d+(?:\.\d+){,2})(?:\.(.*))?",
        lambda match: replace(*match.groups()),
        version,
    )


class LinksHTMLParser(HTMLParser):
    def __init__(self):
        super(LinksHTMLParser, self).__init__()
        self.links = []

    def handle_data(self, data):
        if data and data.strip():
            self.links += [data]


@attr.s
class PytorchWheel(object):
    os_name = attr.ib(type=str, converter=os_to_wheel_name)
    cuda_version = attr.ib(converter=lambda x: "cu{}".format(x) if x else "cpu")
    python = attr.ib(type=str, converter=lambda x: str(x).replace(".", ""))
    torch_version = attr.ib(type=str, converter=fix_version)

    url_template = (
        "http://download.pytorch.org/whl/"
        "{0.cuda_version}/torch-{0.torch_version}-cp{0.python}-cp{0.python}m{0.unicode}-{0.os_name}.whl"
    )

    def __attrs_post_init__(self):
        self.unicode = "u" if self.python.startswith("2") else ""

    def make_url(self):
        # type: () -> Text
        return self.url_template.format(self)


class PytorchResolutionError(FatalSpecsResolutionError):
    pass


class SimplePytorchRequirement(SimpleSubstitution):
    name = "torch"

    packages = ("torch", "torchvision", "torchaudio")

    page_lookup_template = 'https://download.pytorch.org/whl/cu{}/torch_stable.html'
    nightly_page_lookup_template = 'https://download.pytorch.org/whl/nightly/cu{}/torch_nightly.html'
    torch_page_lookup = {
        0: 'https://download.pytorch.org/whl/cpu/torch_stable.html',
        80: 'https://download.pytorch.org/whl/cu80/torch_stable.html',
        90: 'https://download.pytorch.org/whl/cu90/torch_stable.html',
        92: 'https://download.pytorch.org/whl/cu92/torch_stable.html',
        100: 'https://download.pytorch.org/whl/cu100/torch_stable.html',
        101: 'https://download.pytorch.org/whl/cu101/torch_stable.html',
        102: 'https://download.pytorch.org/whl/cu102/torch_stable.html',
        110: 'https://download.pytorch.org/whl/cu110/torch_stable.html',
    }

    def __init__(self, *args, **kwargs):
        super(SimplePytorchRequirement, self).__init__(*args, **kwargs)
        self._matched = False

    def match(self, req):
        # match both any of out packages
        return req.name in self.packages

    def replace(self, req):
        """
        Replace a requirement
        :raises: ValueError if version is pre-release
        """
        # Get rid of +cpu +cu?? etc.
        try:
            req.specs[0] = (req.specs[0][0], req.specs[0][1].split('+')[0])
        except:
            pass
        self._matched = True
        return Text(req)

    def matching_done(self, reqs, package_manager):
        # type: (Sequence[MarkerRequirement], object) -> ()
        if not self._matched:
            return
        # TODO: add conda channel support
        from .pip_api.system import SystemPip
        if package_manager and isinstance(package_manager, SystemPip):
            extra_url, _ = self.get_torch_page(self.cuda_version)
            package_manager.add_extra_install_flags(('-f', extra_url))

    @classmethod
    def get_torch_page(cls, cuda_version, nightly=False):
        # noinspection PyBroadException
        try:
            cuda = int(cuda_version)
        except Exception:
            cuda = 0

        if nightly:
            for c in range(cuda, max(-1, cuda-15), -1):
                # then try the nightly builds, it might be there...
                torch_url = cls.nightly_page_lookup_template.format(c)
                # noinspection PyBroadException
                try:
                    if requests.get(torch_url, timeout=10).ok:
                        print('Torch nightly CUDA {} download page found'.format(c))
                        cls.torch_page_lookup[c] = torch_url
                        return cls.torch_page_lookup[c], c
                except Exception:
                    pass
            return

        # first check if key is valid
        if cuda in cls.torch_page_lookup:
            return cls.torch_page_lookup[cuda], cuda

        # then try a new cuda version page
        for c in range(cuda, max(-1, cuda-15), -1):
            torch_url = cls.page_lookup_template.format(c)
            # noinspection PyBroadException
            try:
                if requests.get(torch_url, timeout=10).ok:
                    print('Torch CUDA {} download page found'.format(c))
                    cls.torch_page_lookup[c] = torch_url
                    return cls.torch_page_lookup[c], c
            except Exception:
                pass

        keys = sorted(cls.torch_page_lookup.keys(), reverse=True)
        for k in keys:
            if k <= cuda:
                return cls.torch_page_lookup[k], k
        # return default - zero
        return cls.torch_page_lookup[0], 0


class PytorchRequirement(SimpleSubstitution):

    name = "torch"
    packages = ("torch", "torchvision", "torchaudio", "torchcsprng", "torchtext")

    def __init__(self, *args, **kwargs):
        os_name = kwargs.pop("os_override", None)
        super(PytorchRequirement, self).__init__(*args, **kwargs)
        self.log = self._session.get_logger(__name__)
        self.package_manager = self.config["agent.package_manager.type"].lower()
        self.os = os_name or self.get_platform()
        self.cuda = "cuda{}".format(self.cuda_version).lower()
        self.python_version_string = str(self.config["agent.default_python"])
        self.python_major_minor_str = '.'.join(self.python_version_string.split('.')[:2])
        if '.' not in self.python_major_minor_str:
            raise PytorchResolutionError(
                "invalid python version {!r} defined in configuration file, key 'agent.default_python': "
                "must have both major and minor parts of the version (for example: '3.7')".format(
                    self.python_version_string
                )
            )
        self.python = "python{}".format(self.python_major_minor_str)

        self.exceptions = [
            PytorchResolutionError(message)
            for message in (
                None,
                'cuda version "{}" is not supported'.format(self.cuda),
                'python version "{}" is not supported'.format(
                    self.python_version_string
                ),
            )
        ]

        try:
            self.validate_python_version()
        except PytorchResolutionError as e:
            self.log.warn("will not be able to install pytorch wheels: %s", e.args[0])

        self._original_req = []

    @property
    def is_conda(self):
        return self.package_manager == "conda"

    @property
    def is_pip(self):
        return not self.is_conda

    def validate_python_version(self):
        """
        Make sure python version has both major and minor versions as required for choosing pytorch wheel
        """
        if self.is_pip and not self.python_major_minor_str:
            raise PytorchResolutionError(
                "invalid python version {!r} defined in configuration file, key 'agent.default_python': "
                "must have both major and minor parts of the version (for example: '3.7')".format(
                    self.python_version_string
                )
            )

    def match(self, req):
        return req.name in self.packages

    @staticmethod
    def get_platform():
        if sys.platform == "linux":
            return "linux"
        if sys.platform == "win32" or sys.platform == "cygwin":
            return "windows"
        if sys.platform == "darwin":
            return "macos"
        raise RuntimeError("unrecognized OS")

    def _get_link_from_torch_page(self, req, torch_url):
        links_parser = LinksHTMLParser()
        links_parser.feed(requests.get(torch_url, timeout=10).text)
        platform_wheel = "win" if self.get_platform() == "windows" else self.get_platform()
        py_ver = self.python_major_minor_str.replace('.', '')
        url = None
        last_v = None
        closest_v = None
        # search for our package
        for l in links_parser.links:
            parts = l.split('/')[-1].split('-')
            if len(parts) < 5:
                continue
            if parts[0] != req.name:
                continue
            # version (ignore +cpu +cu92 etc. + is %2B in the file link)
            # version ignore .postX suffix (treat as regular version)
            # noinspection PyBroadException
            try:
                v = str(parts[1].split('%')[0].split('+')[0])
            except Exception:
                continue
            if len(parts) < 3 or not parts[2].endswith(py_ver):
                continue
            if len(parts) < 5 or platform_wheel not in parts[4]:
                continue
            # update the closest matched version (from above)
            if not closest_v:
                closest_v = v
            elif SimpleVersion.compare_versions(
                    version_a=closest_v, op='>=', version_b=v, num_parts=3) and \
                    SimpleVersion.compare_versions(
                        version_a=v, op='>=', version_b=req.specs[0][1], num_parts=3):
                closest_v = v
            # check if this an actual match
            if not req.compare_version(v) or \
                    (last_v and SimpleVersion.compare_versions(last_v, '>', v, ignore_sub_versions=False)):
                continue

            url = '/'.join(torch_url.split('/')[:-1] + l.split('/'))
            last_v = v
            # if we found an exact match, use it
            # noinspection PyBroadException
            try:
                if req.specs[0][0] == '==' and \
                        SimpleVersion.compare_versions(req.specs[0][1], '==', v, ignore_sub_versions=False):
                    break
            except Exception:
                pass

        return url, last_v or closest_v

    def get_url_for_platform(self, req):
        # check if package is already installed with system packages
        # noinspection PyBroadException
        try:
            if self.config.get("agent.package_manager.system_site_packages", None):
                from pip._internal.commands.show import search_packages_info
                installed_torch = list(search_packages_info([req.name]))
                # notice the comparison order, the first part will make sure we have a valid installed package
                if installed_torch and installed_torch[0]['version'] and \
                        req.compare_version(installed_torch[0]['version']):
                    print('PyTorch: requested "{}" version {}, using pre-installed version {}'.format(
                        req.name, req.specs[0] if req.specs else 'unspecified', installed_torch[0]['version']))
                    # package already installed, do nothing
                    req.specs = [('==', str(installed_torch[0]['version']))]
                    return '{} {} {}'.format(req.name, req.specs[0][0], req.specs[0][1]), True
        except Exception:
            pass

        # make sure we have a specific version to retrieve
        if not req.specs:
            req.specs = [('>', '0')]

        # noinspection PyBroadException
        try:
            req.specs[0] = (req.specs[0][0], req.specs[0][1].split('+')[0])
        except Exception:
            pass
        op, version = req.specs[0]
        # assert op == "=="

        torch_url, torch_url_key = SimplePytorchRequirement.get_torch_page(self.cuda_version)
        url, closest_matched_version = self._get_link_from_torch_page(req, torch_url)
        if not url and self.config.get("agent.package_manager.torch_nightly", None):
            torch_url, torch_url_key = SimplePytorchRequirement.get_torch_page(self.cuda_version, nightly=True)
            url, closest_matched_version = self._get_link_from_torch_page(req, torch_url)
        # try one more time, with a lower cuda version (never fallback to CPU):
        while not url and torch_url_key > 0:
            previous_cuda_key = torch_url_key
            print('Warning, could not locate PyTorch {} matching CUDA version {}, best candidate {}\n'.format(
                    req, previous_cuda_key, closest_matched_version))
            url, closest_matched_version = self._get_link_from_torch_page(req, torch_url)
            if url:
                break
            torch_url, torch_url_key = SimplePytorchRequirement.get_torch_page(int(torch_url_key)-1)
            # never fallback to CPU
            if torch_url_key < 1:
                print(
                    'Error! Could not locate PyTorch version {} matching CUDA version {}'.format(
                        req, previous_cuda_key))
                raise ValueError(
                    'Could not locate PyTorch version {} matching CUDA version {}'.format(req, self.cuda_version))
            else:
                print('Trying PyTorch CUDA version {} support'.format(torch_url_key))

        if not url:
            url = PytorchWheel(
                torch_version=fix_version(version),
                python=self.python_major_minor_str.replace('.', ''),
                os_name=self.os,
                cuda_version=self.cuda_version,
            ).make_url()
        if url:
            # normalize url (sometimes we will get ../ which we should not...
            url = '/'.join(url.split('/')[:3]) + urllib.parse.quote(str(furl(url).path.normalize()))
            # print found
            print('Found PyTorch version {} matching CUDA version {}'.format(req, torch_url_key))

        self.log.debug("checking url: %s", url)
        return url, requests.head(url, timeout=10).ok

    @staticmethod
    def match_version(req, options):
        versioned_options = sorted(
            ((fix_version(key), value) for key, value in options.items()),
            key=itemgetter(0),
            reverse=True,
        )
        req.specs = [(op, fix_version(version)) for op, version in req.specs]

        try:
            return next(
                replacement
                for version, replacement in versioned_options
                if req.compare_version(version)
            )
        except StopIteration:
            raise PytorchResolutionError(
                'Could not find wheel for "{}", '
                "Available versions: {}".format(req, list(options))
            )

    def replace_conda(self, req):
        spec = "".join(req.specs[0]) if req.specs else ""
        if not self.cuda_version:
            return "pytorch-cpu{spec}\ntorchvision-cpu".format(spec=spec)
        return "pytorch{spec}\ntorchvision\ncuda{self.cuda_version}".format(
            self=self, spec=spec
        )

    def _table_lookup(self, req):
        """
        Look for pytorch wheel matching `req` in table
        :param req: python requirement
        """
        def check(base_, key_, exception_):
            result = base_.get(key_)
            if not result:
                if key_.startswith('cuda'):
                    print('Could not locate, {}'.format(exception_))
                    ver = sorted([float(a.replace('cuda', '').replace('none', '0')) for a in base_.keys()], reverse=True)[0]
                    key_ = 'cuda'+str(int(ver))
                    result = base_.get(key_)
                    print('Reverting to \"{}\"'.format(key_))
                    if not result:
                        raise exception_
                    return result
                raise exception_
            if isinstance(result, Exception):
                raise result
            return result

        if self.is_conda:
            return self.replace_conda(req)

        base = self.MAP
        for key, exception in zip((self.os, self.cuda, self.python), self.exceptions):
            base = check(base, key, exception)

        return self.match_version(req, base).replace(" ", "\n")

    def replace(self, req):
        try:
            new_req = self._replace(req)
            if new_req:
                self._original_req.append((req, new_req))
            return new_req
        except Exception as e:
            message = "Exception when trying to resolve python wheel"
            self.log.debug(message, exc_info=True)
            raise PytorchResolutionError("{}: {}".format(message, e))

    def _replace(self, req):
        self.validate_python_version()
        try:
            result, ok = self.get_url_for_platform(req)
            self.log.debug('Replacing requirement "%s" with %r', req, result)
            return result
        except:
            pass

        # try:
        #     result = self._table_lookup(req)
        # except Exception as e:
        #     exc = e
        # else:
        #     self.log.debug('Replacing requirement "%s" with %r', req, result)
        #     return result
        # self.log.debug(
        #     "Could not find Pytorch wheel in table, trying manually constructing URL"
        # )

        result = ok = None
        # try:
        #     result, ok = self.get_url_for_platform(req)
        # except Exception:
        #     pass

        if not ok:
            if result:
                self.log.debug("URL not found: {}".format(result))
            exc = PytorchResolutionError(
                "Could not find pytorch wheel URL for: {} with cuda {} support".format(req, self.cuda_version)
            )
            # cancel exception chaining
            six.raise_from(exc, None)

        self.log.debug('Replacing requirement "%s" with %r', req, result)
        return result

    def replace_back(self, list_of_requirements):  # type: (Dict) -> Dict
        """
        :param list_of_requirements: {'pip': ['a==1.0', ]}
        :return: {'pip': ['a==1.0', ]}
        """
        if not self._original_req:
            return list_of_requirements
        try:
            for k, lines in list_of_requirements.items():
                # k is either pip/conda
                if k not in ('pip', 'conda'):
                    continue
                for i, line in enumerate(lines):
                    if not line or line.lstrip().startswith('#'):
                        continue
                    parts = [p for p in re.split('\s|=|\.|<|>|~|!|@|#', line) if p]
                    if not parts:
                        continue
                    for req, new_req in self._original_req:
                        if req.req.name == parts[0]:
                            # support for pip >= 20.1
                            if '@' in line:
                                # skip if we have nothing to add
                                if str(req).strip() != str(new_req).strip():
                                    # if this is local file and use the version detection
                                    if req.local_file:
                                        lines[i] = '{}'.format(str(new_req))
                                    else:
                                        lines[i] = '{} # {}'.format(str(req), str(new_req))
                            else:
                                lines[i] = '{} # {}'.format(line, str(new_req))
                            break
        except:
            pass

        return list_of_requirements

    MAP = {
        "windows": {
            "cuda100": {
                "python3.7": {
                    "1.0.0": "http://download.pytorch.org/whl/cu100/torch-1.0.0-cp37-cp37m-win_amd64.whl"
                },
                "python3.6": {
                    "1.0.0": "http://download.pytorch.org/whl/cu100/torch-1.0.0-cp36-cp36m-win_amd64.whl"
                },
                "python3.5": {
                    "1.0.0": "http://download.pytorch.org/whl/cu100/torch-1.0.0-cp35-cp35m-win_amd64.whl"
                },
                "python2.7": PytorchResolutionError(
                    "PyTorch does not support Python 2.7 on Windows"
                ),
            },
            "cuda92": {
                "python3.7": {
                    "0.4.1",
                    "http://download.pytorch.org/whl/cu92/torch-0.4.1-cp37-cp37m-win_amd64.whl",
                },
                "python3.6": {
                    "0.4.1": "http://download.pytorch.org/whl/cu92/torch-0.4.1-cp36-cp36m-win_amd64.whl"
                },
                "python3.5": {
                    "0.4.1": "http://download.pytorch.org/whl/cu92/torch-0.4.1-cp35-cp35m-win_amd64.whl"
                },
                "python2.7": PytorchResolutionError(
                    "PyTorch does not support Python 2.7 on Windows"
                ),
            },
            "cuda91": {
                "python3.6": {
                    "0.4.0": "http://download.pytorch.org/whl/cu91/torch-0.4.0-cp36-cp36m-win_amd64.whl"
                },
                "python3.5": {
                    "0.4.0": "http://download.pytorch.org/whl/cu91/torch-0.4.0-cp35-cp35m-win_amd64.whl"
                },
                "python2.7": PytorchResolutionError(
                    "PyTorch does not support Python 2.7 on Windows"
                ),
            },
            "cuda90": {
                "python3.6": {
                    "0.4.0": "http://download.pytorch.org/whl/cu90/torch-0.4.0-cp36-cp36m-win_amd64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cu90/torch-1.0.0-cp36-cp36m-win_amd64.whl",
                },
                "python3.5": {
                    "0.4.0": "http://download.pytorch.org/whl/cu90/torch-0.4.0-cp35-cp35m-win_amd64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cu90/torch-1.0.0-cp35-cp35m-win_amd64.whl",
                },
                "python2.7": PytorchResolutionError(
                    "PyTorch does not support Python 2.7 on Windows"
                ),
            },
            "cuda80": {
                "python3.6": {
                    "0.4.0": "http://download.pytorch.org/whl/cu80/torch-0.4.0-cp36-cp36m-win_amd64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cu80/torch-1.0.0-cp36-cp36m-win_amd64.whl",
                },
                "python3.5": {
                    "0.4.0": "http://download.pytorch.org/whl/cu80/torch-0.4.0-cp35-cp35m-win_amd64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cu80/torch-1.0.0-cp35-cp35m-win_amd64.whl",
                },
                "python2.7": PytorchResolutionError(
                    "PyTorch does not support Python 2.7 on Windows"
                ),
            },
            "cudanone": {
                "python3.6": {
                    "0.4.0": "http://download.pytorch.org/whl/cpu/torch-0.4.0-cp36-cp36m-win_amd64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cpu/torch-1.0.0-cp36-cp36m-win_amd64.whl",
                },
                "python3.5": {
                    "0.4.0": "http://download.pytorch.org/whl/cpu/torch-0.4.0-cp35-cp35m-win_amd64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cpu/torch-1.0.0-cp35-cp35m-win_amd64.whl",
                },
                "python2.7": PytorchResolutionError(
                    "PyTorch does not support Python 2.7 on Windows"
                ),
            },
        },
        "macos": {
            "cuda100": PytorchResolutionError(
                "MacOS Binaries dont support CUDA, install from source if CUDA is needed"
            ),
            "cuda92": PytorchResolutionError(
                "MacOS Binaries dont support CUDA, install from source if CUDA is needed"
            ),
            "cuda91": PytorchResolutionError(
                "MacOS Binaries dont support CUDA, install from source if CUDA is needed"
            ),
            "cuda90": PytorchResolutionError(
                "MacOS Binaries dont support CUDA, install from source if CUDA is needed"
            ),
            "cuda80": PytorchResolutionError(
                "MacOS Binaries dont support CUDA, install from source if CUDA is needed"
            ),
            "cudanone": {
                "python3.6": {"0.4.0": "torch"},
                "python3.5": {"0.4.0": "torch"},
                "python2.7": {"0.4.0": "torch"},
            },
        },
        "linux": {
            "cuda100": {
                "python3.7": {
                    "1.0.0": "http://download.pytorch.org/whl/cu100/torch-1.0.0-cp37-cp37m-linux_x86_64.whl",
                    "1.0.1": "http://download.pytorch.org/whl/cu100/torch-1.0.1-cp37-cp37m-linux_x86_64.whl",
                    "1.1.0": "http://download.pytorch.org/whl/cu100/torch-1.1.0-cp37-cp37m-linux_x86_64.whl",
                    "1.2.0": "http://download.pytorch.org/whl/cu100/torch-1.2.0-cp37-cp37m-manylinux1_x86_64.whl",
                },
                "python3.6": {
                    "1.0.0": "http://download.pytorch.org/whl/cu100/torch-1.0.0-cp36-cp36m-linux_x86_64.whl",
                    "1.0.1": "http://download.pytorch.org/whl/cu100/torch-1.0.1-cp36-cp36m-linux_x86_64.whl",
                    "1.1.0": "http://download.pytorch.org/whl/cu100/torch-1.1.0-cp36-cp36m-linux_x86_64.whl",
                    "1.2.0": "http://download.pytorch.org/whl/cu100/torch-1.2.0-cp36-cp36m-manylinux1_x86_64.whl",
                },
                "python3.5": {
                    "1.0.0": "http://download.pytorch.org/whl/cu100/torch-1.0.0-cp35-cp35m-linux_x86_64.whl",
                    "1.0.1": "http://download.pytorch.org/whl/cu100/torch-1.0.1-cp35-cp35m-linux_x86_64.whl",
                    "1.1.0": "http://download.pytorch.org/whl/cu100/torch-1.1.0-cp35-cp35m-linux_x86_64.whl",
                    "1.2.0": "http://download.pytorch.org/whl/cu100/torch-1.2.0-cp35-cp35m-manylinux1_x86_64.whl",
                },
                "python2.7": {
                    "1.0.0": "http://download.pytorch.org/whl/cu100/torch-1.0.0-cp27-cp27mu-linux_x86_64.whl",
                    "1.0.1": "http://download.pytorch.org/whl/cu100/torch-1.0.1-cp27-cp27mu-linux_x86_64.whl",
                    "1.1.0": "http://download.pytorch.org/whl/cu100/torch-1.1.0-cp27-cp27mu-linux_x86_64.whl",
                    "1.2.0": "http://download.pytorch.org/whl/cu100/torch-1.2.0-cp27-cp27mu-manylinux1_x86_64.whl",
                },
            },
            "cuda92": {
                "python3.7": {
                    "0.4.1": "http://download.pytorch.org/whl/cu92/torch-0.4.1.post2-cp37-cp37m-linux_x86_64.whl",
                    "1.2.0": "https://download.pytorch.org/whl/cu92/torch-1.2.0%2Bcu92-cp37-cp37m-manylinux1_x86_64.whl"
                },
                "python3.6": {
                    "0.4.1": "http://download.pytorch.org/whl/cu92/torch-0.4.1-cp36-cp36m-linux_x86_64.whl",
                    "1.2.0": "https://download.pytorch.org/whl/cu92/torch-1.2.0%2Bcu92-cp36-cp36m-manylinux1_x86_64.whl"
                },
                "python3.5": {
                    "0.4.1": "http://download.pytorch.org/whl/cu92/torch-0.4.1-cp35-cp35m-linux_x86_64.whl",
                    "1.2.0": "https://download.pytorch.org/whl/cu92/torch-1.2.0%2Bcu92-cp35-cp35m-manylinux1_x86_64.whl"
                },
                "python2.7": {
                    "0.4.1": "http://download.pytorch.org/whl/cu92/torch-0.4.1-cp27-cp27mu-linux_x86_64.whl",
                    "1.2.0": "https://download.pytorch.org/whl/cu92/torch-1.2.0%2Bcu92-cp27-cp27mu-manylinux1_x86_64.whl"
                },
            },
            "cuda91": {
                "python3.6": {
                    "0.4.0": "http://download.pytorch.org/whl/cu91/torch-0.4.0-cp36-cp36m-linux_x86_64.whl"
                },
                "python3.5": {
                    "0.4.0": "http://download.pytorch.org/whl/cu91/torch-0.4.0-cp35-cp35m-linux_x86_64.whl"
                },
                "python2.7": {
                    "0.4.0": "http://download.pytorch.org/whl/cu91/torch-0.4.0-cp27-cp27mu-linux_x86_64.whl"
                },
            },
            "cuda90": {
                "python3.6": {
                    "0.4.0": "http://download.pytorch.org/whl/cu90/torch-0.4.0-cp36-cp36m-linux_x86_64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cu90/torch-1.0.0-cp36-cp36m-linux_x86_64.whl",
                },
                "python3.5": {
                    "0.4.0": "http://download.pytorch.org/whl/cu90/torch-0.4.0-cp35-cp35m-linux_x86_64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cu90/torch-1.0.0-cp35-cp35m-linux_x86_64.whl",
                },
                "python2.7": {
                    "0.4.0": "http://download.pytorch.org/whl/cu90/torch-0.4.0-cp27-cp27mu-linux_x86_64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cu90/torch-1.0.0-cp27-cp27mu-linux_x86_64.whl",
                },
            },
            "cuda80": {
                "python3.6": {
                    "0.4.1": "http://download.pytorch.org/whl/cu80/torch-0.4.1-cp36-cp36m-linux_x86_64.whl",
                    "0.3.1": "torch==0.3.1",
                    "0.3.0.post4": "torch==0.3.0.post4",
                    "0.1.2.post1": "torch==0.1.2.post1",
                    "0.1.2": "torch==0.1.2",
                    "1.0.0": "http://download.pytorch.org/whl/cu80/torch-1.0.0-cp36-cp36m-linux_x86_64.whl",
                },
                "python3.5": {
                    "0.4.1": "http://download.pytorch.org/whl/cu80/torch-0.4.1-cp35-cp35m-linux_x86_64.whl",
                    "0.3.1": "torch==0.3.1",
                    "0.3.0.post4": "torch==0.3.0.post4",
                    "0.1.2.post1": "torch==0.1.2.post1",
                    "0.1.2": "torch==0.1.2",
                    "1.0.0": "http://download.pytorch.org/whl/cu80/torch-1.0.0-cp35-cp35m-linux_x86_64.whl",
                },
                "python2.7": {
                    "0.4.1": "http://download.pytorch.org/whl/cu80/torch-0.4.1-cp27-cp27mu-linux_x86_64.whl",
                    "0.3.1": "torch==0.3.1",
                    "0.3.0.post4": "torch==0.3.0.post4",
                    "0.1.2.post1": "torch==0.1.2.post1",
                    "0.1.2": "torch==0.1.2",
                    "1.0.0": "http://download.pytorch.org/whl/cu80/torch-1.0.0-cp27-cp27mu-linux_x86_64.whl",
                },
            },
            "cudanone": {
                "python3.6": {
                    "0.4.0": "http://download.pytorch.org/whl/cpu/torch-0.4.0-cp36-cp36m-linux_x86_64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cpu/torch-1.0.0-cp36-cp36m-linux_x86_64.whl",
                },
                "python3.5": {
                    "0.4.0": "http://download.pytorch.org/whl/cpu/torch-0.4.0-cp35-cp35m-linux_x86_64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cpu/torch-1.0.0-cp35-cp35m-linux_x86_64.whl",
                },
                "python2.7": {
                    "0.4.0": "http://download.pytorch.org/whl/cpu/torch-0.4.0-cp27-cp27mu-linux_x86_64.whl",
                    "1.0.0": "http://download.pytorch.org/whl/cpu/torch-1.0.0-cp27-cp27mu-linux_x86_64.whl",
                },
            },
        },
    }
