import re

from io import BytesIO
from collections import OrderedDict
from xml.etree.ElementTree import ElementTree

import requests


def get_imagery_layers(url):
    """
    Get the list of available image layers that can be used as background
    or foreground based on the URL to a WTML (WorldWide Telescope image
    collection file).

    Parameters
    ----------
    url : `str`
        The URL of the image collection.
    """

    available_layers = OrderedDict()

    # Get the XML describing the available surveys
    response = requests.get(url)
    assert response.ok
    b = BytesIO(response.content)
    e = ElementTree()
    t = e.parse(b)

    # For now only look at the ImageSets at the root of the
    # XML since these seem to be the main surveys.
    for survey in t.findall('ImageSet'):
        name = survey.attrib['Name']
        thumbnail_url = survey.find('ThumbnailUrl').text
        if not thumbnail_url:
            thumbnail_url = None
        available_layers[name] = {'thumbnail': thumbnail_url}

    return available_layers


class ImageryLayers():
    """
    A supplemental class that standardizes layer names, sorts them by
    bandpass, and makes it possible to tab complete them.
    """

    def __init__(self, layer_list):
        self._layers = {}
        self._spectrum = ['gamma', 'x', 'uv', 'visible',
                          'ir', 'micro', 'radio', 'other']
        self.integers = ['zero', 'one', 'two', 'three', 'four',
                         'five', 'six', 'seven', 'eight', 'nine']
        for band in self._spectrum:
            self._layers[band] = {}

        self._list2dict(layer_list)

    def _list2dict(self, og_list):
        # Helps turn the list of layer names used to initialize the class
        # (og_list) into a dict.
        for layer in og_list:

            if re.search(r'(?i)gamma',layer) is not None:
                self._add2dict(self._layers, layer, 'gamma')
                continue # automatically advance to next iteration

            if re.search(r'(?i)x(-|\s)?ray',layer) is not None:
                self._add2dict(self._layers, layer, 'x')
                continue

            if (re.search(r'(?i)ultra(-|\s)?violet',layer) is not None or
                re.search(r'(?i)[^\d\w]+uv|uv[^\d\w]+',layer) is not None):
                self._add2dict(self._layers, layer, 'uv')
                continue

            if (re.search(r'(?i)optical',layer) is not None or
                re.search(r'(?i)visible',layer) is not None):
                self._add2dict(self._layers, layer, 'visible')
                continue

            if (re.search(r'(?i)infrared',layer) is not None or
                re.search(r'(?i)[^\d\w]+ir|ir[^\d\w]+',layer) is not None):
                self._add2dict(self._layers, layer, 'ir')
                continue

            if (re.search(r'(?i)microwave',layer) is not None or
                re.search(r'(?i)[^\d\w]+cmb|cmb[^\d\w]+',layer) is not None):
                self._add2dict(self._layers, layer, 'micro')
                continue

            if re.search(r'(?i)radio',layer) is not None:
                self._add2dict(self._layers, layer, 'radio')
                continue

            self._add2dict(self._layers, layer, 'other')

    def _add2dict(self, diction, full_layer, bandpass):
        # Handles a layer's (full_layer) actual addition to the master
        # dict of layers (diction) according to its bandpass.
        suffix = ''
        short = self._shorten(full_layer) + suffix

        while short in diction[bandpass]:
            if suffix:
                suffix += 1
            else:
                suffix = 1

            short += str(suffix)

        diction[bandpass][short] = {}
        diction[bandpass][short]['full_name'] = full_layer

    def _shorten(self, string):
        # Unlocks tab completion by shortening a full layer's name
        # (string) to a valid Python name based on its first word.
        cut_left = re.search(r'^[_\W]+', string)
        if cut_left is not None:
            string = string[cut_left.end():]

        cut_right = re.search(r'[_\W]', string)
        if cut_right is not None:
            string = string[:cut_right.start()].lower()

        digit = re.search(r'^\d', string)
        if digit is not None:
            for i, num in enumerate(self.integers, 0):
                if str(i) == digit.group(0):
                    string = string[:digit.start()] + num + string[digit.end():]
                    break

        return string

    def __dir__(self):
        return sorted(self._layers.keys())

    def __getattr__(self, band):
        return Bandpass(self._layers[band])


class Bandpass():
    """
    Allows the __getattr__() method from ImageryLayers to reach into the
    inner dict corresponding to layers of a particular bandpass.
    """

    def __init__(self, band):
        self._band = band

    def __dir__(self):
        return sorted(self._band.keys())

    def __getattr__(self, name):
        return self._band[name]['full_name']
