#!/usr/bin/env python
# requirements_to_rst.py
# Copyright (c) 2013-2017 Pablo Acosta-Serafini
# See LICENSE for details
# pylint: disable=C0103,C0111,R0912,R0914,R0915

import os
import textwrap

from sbin.functions import SUPPORTED_VERS, json_load


###
# Global variables
###
LINE_WIDTH = 72


###
# Functions
###
def def_links(mobj):
    """ Define Sphinx requirements links """
    fdict = json_load(os.path.join('data', 'requirements.json'))
    sdeps = sorted(fdict.keys())
    olines = []
    for item in sdeps:
        olines.append(
            '.. _{name}: {url}\n'.format(
                name=fdict[item]['name'],
                url=fdict[item]['url']
            )
        )
    ret = []
    for line in olines:
        wobj = textwrap.wrap(
            line,
            width=LINE_WIDTH,
            subsequent_indent='   '
        )
        ret.append('\n'.join([item for item in wobj]))
    mobj.out('\n'.join(ret))


def make_common_entry(plist, pyver, suffix, req_ver):
    """ Generate Python interpreter version entries for 2.x or 3.x series """
    prefix = 'Python {pyver}.x{suffix}'.format(
        pyver=pyver, suffix=suffix
    )
    plist.append(
        '{prefix}{ver}'.format(
            prefix=prefix,
            ver=ops_to_words(req_ver)
        )
    )


def make_multi_entry(plist, pkg_pyvers, ver_dict):
    """ Generate Python interpreter version entries """
    for pyver in pkg_pyvers:
        pver = pyver[2]+'.'+pyver[3:]
        plist.append(
            'Python {0}: {1}'.format(pver, ops_to_words(ver_dict[pyver]))
        )


def op_to_words(item):
    """ Translate >=, ==, <= to words """
    sdicts = [
        {'==': ''},
        {'>=': ' or newer'},
        {'>': 'newer than '},
        {'<=': ' or older'},
        {'<': 'older than '},
        {'!=': 'except '}
    ]
    for sdict in sdicts:
        prefix = list(sdict.keys())[0]
        suffix = sdict[prefix]
        if item.startswith(prefix):
            if prefix == '==':
                return item[2:]
            elif prefix == '!=':
                return suffix+item[2:]
            elif prefix in ['>', '<']:
                return suffix+item[1:]
            else:
                return item[2:]+suffix
    raise RuntimeError('Inequality not supported')


def ops_to_words(item):
    """ Translate requirement specification to words """
    unsupp_ops = ['~=', '===']
    # Ordered for  "pleasant" word specification
    supp_ops = ['>=', '>', '==', '<=', '<', '!=']
    tokens = sorted(item.split(','), reverse=True)
    actual_tokens = []
    for req in tokens:
        for op in unsupp_ops:
            if req.startswith(op):
                raise RuntimeError(
                    'Unsupported version specification: {0}'.format(op)
                )
        for op in supp_ops:
            if req.startswith(op):
                actual_tokens.append(op)
                break
        else:
            raise RuntimeError(
                'Illegal comparison operator: {0}'.format(op)
            )
    if len(list(set(actual_tokens))) != len(actual_tokens):
        raise RuntimeError('Multiple comparison operators of the same type')
    if '!=' in actual_tokens:
        return (
            ' and '.join([op_to_words(token) for token in tokens[:-1]])
            +' '+
            op_to_words(tokens[-1])
        )
    else:
        return ' and '.join([op_to_words(token) for token in tokens])


def proc_requirements(mobj):
    """ Get requirements in reStructuredText format """
    pyvers = ['py{0}'.format(item.replace('.', '')) for item in SUPPORTED_VERS]
    py2vers = sorted([item for item in pyvers if item.startswith('py2')])
    py3vers = sorted([item for item in pyvers if item.startswith('py3')])
    fdict = json_load(os.path.join('data', 'requirements.json'))
    olines = ['']
    sdict = dict([(item['name'], item) for item in fdict.values()])
    for real_name in sorted(sdict.keys()):
        pkg_dict = sdict[real_name]
        if pkg_dict['cat'] == ['rtd']:
            continue
        plist = [] if not pkg_dict['optional'] else ['optional']
        # Convert instances that have a single version for all Python
        # interpreters into a full dictionary of Python interpreter and
        # package versions # so as to apply the same algorithm in all cases
        if isinstance(pkg_dict['ver'], str):
            pkg_dict['ver'] = dict(
                [(pyver, pkg_dict['ver']) for pyver in pyvers]
            )
        pkg_pyvers = sorted(pkg_dict['ver'].keys())
        pkg_py2vers = sorted(
            [
                item
                for item in pkg_dict['ver'].keys()
                if item.startswith('py2')
            ]
        )
        req_vers = list(set(pkg_dict['ver'].values()))
        req_py2vers = list(
            set(
                [
                    pkg_dict['ver'][item]
                    for item in py2vers
                    if item in pkg_dict['ver']
                ]
            )
        )
        req_py3vers = list(
            set(
                [
                    pkg_dict['ver'][item]
                    for item in py3vers
                    if item in pkg_dict['ver']
                ]
            )
        )
        if (len(req_vers) == 1) and (pkg_pyvers == pyvers):
            plist.append(ops_to_words(req_vers[0]))
        elif ((pkg_pyvers == pyvers) and (len(req_py2vers) == 1)
             and (len(req_py3vers) == 1)):
            make_common_entry(plist, '2', ': ', req_py2vers[0])
            make_common_entry(plist, '3', ': ', req_py3vers[0])
        elif ((pkg_pyvers == pyvers) and (len(req_py2vers) == len(py2vers)) and
             (len(req_py3vers) == 1) and
             (pkg_dict['ver'][pkg_py2vers[-1]] == req_py3vers[0])):
            py2dict = dict(
                [
                    (key, value)
                    for key, value in pkg_dict['ver'].items()
                    if key.startswith('py2') and (key != pkg_py2vers[-1])
                ]
            )
            make_multi_entry(plist, py2vers[:-1], py2dict)
            pver = pkg_py2vers[-1][2]+'.'+pkg_py2vers[-1][3:]
            plist.append(
                'Python {pyver} or newer: {ver}'.format(
                    pyver=pver,
                    ver=ops_to_words(req_py3vers[0])
                )
            )
        elif ((pkg_pyvers == pyvers) and (len(req_py2vers) == len(py2vers)) and
             (len(req_py3vers) == 1)):
            py2dict = dict(
                [
                    (key, value)
                    for key, value in pkg_dict['ver'].items()
                    if key.startswith('py2')
                ]
            )
            make_multi_entry(plist, py2vers, py2dict)
            make_common_entry(plist, '3', ': ', req_py3vers[0])
        elif ((pkg_pyvers == pyvers) and (len(req_py3vers) == len(py3vers)) and
             (len(req_py2vers) == 1)):
            py3dict = dict(
                [
                    (key, value)
                    for key, value in pkg_dict['ver'].items()
                    if key.startswith('py3')
                ]
            )
            make_common_entry(plist, '2', ': ', req_py2vers[0])
            make_multi_entry(plist, py3vers, py3dict)
        elif (len(req_vers) == 1) and (pkg_pyvers == py2vers):
            make_common_entry(plist, '2', ' only, ', req_vers[0])
        elif (len(req_vers) == 1) and (pkg_pyvers == py3vers):
            make_common_entry(plist, '3', ' only, ', req_vers[0])
        else:
            make_multi_entry(plist, pkg_pyvers, pkg_dict['ver'])
        olines.append(
            '    * `{name}`_ ({par})'.format(
                name=pkg_dict['name'],
                par=', '.join(plist)
            )
        )
    ret = []
    for line in olines:
        wobj = textwrap.wrap(
            line,
            width=LINE_WIDTH,
            subsequent_indent='      '
        )
        ret.append('\n'.join([item for item in wobj]))

    mobj.out('\n\n'.join(ret)+'\n\n')
