"""Fix changes imports of urllib which are now incompatible.
   This is rather similar to fix_imports, but because of the more
   complex nature of the fixing for urllib, it has its own fixer.
"""
# Author: Nick Edds

# Local imports
from .fix_imports import alternates, FixImports
from .. import fixer_base
from ..fixer_util import Name, Comma, FromImport, Newline, attr_chain

MAPPING = {'urllib':  [
                ('urllib.request',
                    ['URLOpener', 'FancyURLOpener', 'urlretrieve',
                     '_urlopener', 'urlopen', 'urlcleanup']),
                ('urllib.parse',
                    ['quote', 'quote_plus', 'unquote', 'unquote_plus',
                     'urlencode', 'pathname2url', 'url2pathname', 'splitattr',
                     'splithost', 'splitnport', 'splitpasswd', 'splitport',
                     'splitquery', 'splittag', 'splittype', 'splituser',
                     'splitvalue', ]),
                ('urllib.error',
                    ['ContentTooShortError'])],
           'urllib2' : [
                ('urllib.request',
                    ['urlopen', 'install_opener', 'build_opener',
                     'Request', 'OpenerDirector', 'BaseHandler',
                     'HTTPDefaultErrorHandler', 'HTTPRedirectHandler',
                     'HTTPCookieProcessor', 'ProxyHandler',
                     'HTTPPasswordMgr',
                     'HTTPPasswordMgrWithDefaultRealm',
                     'AbstractBasicAuthHandler',
                     'HTTPBasicAuthHandler', 'ProxyBasicAuthHandler',
                     'AbstractDigestAuthHandler',
                     'HTTPDigestAuthHandler', 'ProxyDigestAuthHandler',
                     'HTTPHandler', 'HTTPSHandler', 'FileHandler',
                     'FTPHandler', 'CacheFTPHandler',
                     'UnknownHandler']),
                ('urllib.error',
                    ['URLError', 'HTTPError']),
           ]
}

# Duplicate the url parsing functions for urllib2.
MAPPING["urllib2"].append(MAPPING["urllib"][1])


def build_pattern():
    bare = set()
    for old_module, changes in MAPPING.items():
        for change in changes:
            new_module, members = change
            members = alternates(members)
            yield """import_name< 'import' (module=%r
                                  | dotted_as_names< any* module=%r any* >) >
                  """ % (old_module, old_module)
            yield """import_from< 'from' mod_member=%r 'import'
                       ( member=%s | import_as_name< member=%s 'as' any > |
                         import_as_names< members=any*  >) >
                  """ % (old_module, members, members)
            yield """import_from< 'from' module_star=%r 'import' star='*' >
                  """ % old_module
            yield """import_name< 'import'
                                  dotted_as_name< module_as=%r 'as' any > >
                  """ % old_module
            yield """power< module_dot=%r trailer< '.' member=%s > any* >
                  """ % (old_module, members)


class FixUrllib(FixImports):

    def build_pattern(self):
        return "|".join(build_pattern())

    def transform_import(self, node, results):
        """Transform for the basic import case. Replaces the old
           import name with a comma separated list of its
           replacements.
        """
        import_mod = results.get('module')
        pref = import_mod.prefix

        names = []

        # create a Node list of the replacement modules
        for name in MAPPING[import_mod.value][:-1]:
            names.extend([Name(name[0], prefix=pref), Comma()])
        names.append(Name(MAPPING[import_mod.value][-1][0], prefix=pref))
        import_mod.replace(names)

    def transform_member(self, node, results):
        """Transform for imports of specific module elements. Replaces
           the module to be imported from with the appropriate new
           module.
        """
        mod_member = results.get('mod_member')
        pref = mod_member.prefix
        member = results.get('member')

        # Simple case with only a single member being imported
        if member:
            # this may be a list of length one, or just a node
            if isinstance(member, list):
                member = member[0]
            new_name = None
            for change in MAPPING[mod_member.value]:
                if member.value in change[1]:
                    new_name = change[0]
                    break
            if new_name:
                mod_member.replace(Name(new_name, prefix=pref))
            else:
                self.cannot_convert(node,
                                    'This is an invalid module element')

        # Multiple members being imported
        else:
            # a dictionary for replacements, order matters
            modules = []
            mod_dict = {}
            members = results.get('members')
            for member in members:
                member = member.value
                # we only care about the actual members
                if member != ',':
                    for change in MAPPING[mod_member.value]:
                        if member in change[1]:
                            if change[0] in mod_dict:
                                mod_dict[change[0]].append(member)
                            else:
                                mod_dict[change[0]] = [member]
                                modules.append(change[0])

            new_nodes = []
            for module in modules:
                elts = mod_dict[module]
                names = []
                for elt in elts[:-1]:
                    names.extend([Name(elt, prefix=pref), Comma()])
                names.append(Name(elts[-1], prefix=pref))
                new_nodes.append(FromImport(module, names))
            if new_nodes:
                nodes = []
                for new_node in new_nodes[:-1]:
                    nodes.extend([new_node, Newline()])
                nodes.append(new_nodes[-1])
                node.replace(nodes)
            else:
                self.cannot_convert(node, 'All module elements are invalid')

    def transform_dot(self, node, results):
        """Transform for calls to module members in code."""
        module_dot = results.get('module_dot')
        member = results.get('member')
        # this may be a list of length one, or just a node
        if isinstance(member, list):
            member = member[0]
        new_name = None
        for change in MAPPING[module_dot.value]:
            if member.value in change[1]:
                new_name = change[0]
                break
        if new_name:
            module_dot.replace(Name(new_name,
                                    prefix=module_dot.prefix))
        else:
            self.cannot_convert(node, 'This is an invalid module element')

    def transform(self, node, results):
        if results.get('module'):
            self.transform_import(node, results)
        elif results.get('mod_member'):
            self.transform_member(node, results)
        elif results.get('module_dot'):
            self.transform_dot(node, results)
        # Renaming and star imports are not supported for these modules.
        elif results.get('module_star'):
            self.cannot_convert(node, 'Cannot handle star imports.')
        elif results.get('module_as'):
            self.cannot_convert(node, 'This module is now multiple modules')
