"""Functions new to the pyScss library."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import logging
import os.path
import random

import six
from six.moves import xrange

from scss import config
from scss.errors import SassMissingDependency
from scss.extension import Extension
from scss.namespace import Namespace
from scss.types import Color, Number, String, List
from scss.util import escape, make_data_url, make_filename_hash

try:
    from PIL import Image, ImageDraw
except ImportError:
    try:
        import Image
        import ImageDraw
    except ImportError:
        Image = None
        ImageDraw = None


log = logging.getLogger(__name__)


class ExtraExtension(Extension):
    """Extra functions unique to the pyScss library."""
    name = 'extra'
    namespace = Namespace()


# Alias to make the below declarations less noisy
ns = ExtraExtension.namespace


# ------------------------------------------------------------------------------
# Image stuff

def _image_noise(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, background=None):
    if not density:
        density = [0.8]
    elif not isinstance(density, (tuple, list)):
        density = [density]

    if not intensity:
        intensity = [0.5]
    elif not isinstance(intensity, (tuple, list)):
        intensity = [intensity]

    if not color:
        color = [(0, 0, 0, 0)]
    elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)):
        color = [color]

    if not opacity:
        opacity = [0.2]
    elif not isinstance(opacity, (tuple, list)):
        opacity = [opacity]

    if not monochrome:
        monochrome = [False]
    elif not isinstance(monochrome, (tuple, list)):
        monochrome = [monochrome]

    pixels = {}

    if background:
        for y in xrange(size):
            for x in xrange(size):
                ca = float(background[3])
                pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca)

    loops = max(map(len, (density, intensity, color, opacity, monochrome)))
    for l in range(loops):
        _density = density[l % len(density)]
        _intensity = intensity[l % len(intensity)]
        _color = color[l % len(color)]
        _opacity = opacity[l % len(opacity)]
        _monochrome = monochrome[l % len(monochrome)]
        _intensity = 1 - _intensity
        if _intensity < 0.5:
            cx = 255 * _intensity
            cm = cx
        else:
            cx = 255 * (1 - _intensity)
            cm = 255 * _intensity
        xa = int(cm - cx)
        xb = int(cm + cx)
        if xa > 0:
            xa &= 255
        else:
            xa = 0
        if xb > 0:
            xb &= 255
        else:
            xb = 0
        r, g, b, a = _color
        for i in xrange(int(round(_density * size ** 2))):
            x = random.randint(1, size)
            y = random.randint(1, size)
            cc = random.randint(xa, xb)
            cr = (cc) * (1 - a) + a * r
            cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g
            cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b
            ca = random.random() * _opacity
            ica = 1 - ca
            pos = (x - 1, y - 1)
            dst = pixels.get(pos, (0, 0, 0, 0))
            src = (cr * ca, cg * ca, cb * ca, ca)
            pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica)

    for pos, col in pixels.items():
        ca = col[3]
        if ca:
            pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255))


def _image_brushed(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, direction=None, spread=None, background=None):
    if not density:
        density = [0.8]
    elif not isinstance(density, (tuple, list)):
        density = [density]

    if not intensity:
        intensity = [0.5]
    elif not isinstance(intensity, (tuple, list)):
        intensity = [intensity]

    if not color:
        color = [(0, 0, 0, 0)]
    elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)):
        color = [color]

    if not opacity:
        opacity = [0.2]
    elif not isinstance(opacity, (tuple, list)):
        opacity = [opacity]

    if not monochrome:
        monochrome = [False]
    elif not isinstance(monochrome, (tuple, list)):
        monochrome = [monochrome]

    if not direction:
        direction = [0]
    elif not isinstance(direction, (tuple, list)):
        direction = [direction]

    if not spread:
        spread = [0]
    elif not isinstance(spread, (tuple, list)):
        spread = [spread]

    def ppgen(d):
        if d is None:
            return
        d = d % 4
        if d == 0:
            pp = lambda x, y, o: ((x - o) % size, y)
        elif d == 1:
            pp = lambda x, y, o: ((x - o) % size, (y + x - o) % size)
        elif d == 2:
            pp = lambda x, y, o: (y, (x - o) % size)
        else:
            pp = lambda x, y, o: ((x - o) % size, (y - x - o) % size)
        return pp

    pixels = {}

    if background:
        for y in xrange(size):
            for x in xrange(size):
                ca = float(background[3])
                pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca)

    loops = max(map(len, (density, intensity, color, opacity, monochrome, direction, spread)))
    for l in range(loops):
        _density = density[l % len(density)]
        _intensity = intensity[l % len(intensity)]
        _color = color[l % len(color)]
        _opacity = opacity[l % len(opacity)]
        _monochrome = monochrome[l % len(monochrome)]
        _direction = direction[l % len(direction)]
        _spread = spread[l % len(spread)]
        _intensity = 1 - _intensity
        if _intensity < 0.5:
            cx = 255 * _intensity
            cm = cx
        else:
            cx = 255 * (1 - _intensity)
            cm = 255 * _intensity
        xa = int(cm - cx)
        xb = int(cm + cx)
        if xa > 0:
            xa &= 255
        else:
            xa = 0
        if xb > 0:
            xb &= 255
        else:
            xb = 0
        r, g, b, a = _color
        pp = ppgen(_direction)
        if pp:
            for y in xrange(size):
                if _spread and (y + (l % 2)) % _spread:
                    continue
                o = random.randint(1, size)
                cc = random.randint(xa, xb)
                cr = (cc) * (1 - a) + a * r
                cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g
                cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b
                da = random.randint(0, 255) * _opacity
                ip = round((size / 2.0 * _density) / int(1 / _density))
                iq = round((size / 2.0 * (1 - _density)) / int(1 / _density))
                if ip:
                    i = da / ip
                    aa = 0
                else:
                    i = 0
                    aa = da
                d = 0
                p = ip
                for x in xrange(size):
                    if d == 0:
                        if p > 0:
                            p -= 1
                            aa += i
                        else:
                            d = 1
                            q = iq
                    elif d == 1:
                        if q > 0:
                            q -= 1
                        else:
                            d = 2
                            p = ip
                    elif d == 2:
                        if p > 0:
                            p -= 1
                            aa -= i
                        else:
                            d = 3
                            q = iq
                    elif d == 3:
                        if q > 0:
                            q -= 1
                        else:
                            d = 0
                            p = ip
                    if aa > 0:
                        ca = aa / 255.0
                    else:
                        ca = 0.0
                    ica = 1 - ca
                    pos = pp(x, y, o)
                    dst = pixels.get(pos, (0, 0, 0, 0))
                    src = (cr * ca, cg * ca, cb * ca, ca)
                    pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica)

    for pos, col in pixels.items():
        ca = col[3]
        if ca:
            pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255))


@ns.declare
def background_noise(density=None, opacity=None, size=None, monochrome=False, intensity=(), color=None, background=None, inline=False):
    if not Image:
        raise SassMissingDependency('PIL', 'image manipulation')

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else 0
    if size < 1 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    background = Color(background).value if background else None

    new_image = Image.new(
        mode='RGBA',
        size=(size, size)
    )

    pixdata = new_image.load()
    _image_noise(pixdata, size, density, intensity, color, opacity, monochrome)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome)
        asset_file = 'noise-%s%sx%s' % ('mono-' if monochrome else '', size, size)
        # asset_file += '-[%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []))
        asset_file += '-' + make_filename_hash(key)
        asset_file += '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = make_data_url('image/png', contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)


@ns.declare
def background_brushed(density=None, intensity=None, color=None, opacity=None, size=None, monochrome=False, direction=(), spread=(), background=None, inline=False):
    if not Image:
        raise SassMissingDependency('PIL', 'image manipulation')

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else -1
    if size < 0 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    direction = [Number(v).value for v in List.from_maybe(direction)]
    spread = [Number(v).value for v in List.from_maybe(spread)]

    background = Color(background).value if background else None

    new_image = Image.new(
        mode='RGBA',
        size=(size, size)
    )

    pixdata = new_image.load()
    _image_brushed(pixdata, size, density, intensity, color, opacity, monochrome, direction, spread, background)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome, direction, spread, background)
        asset_file = 'brushed-%s%sx%s' % ('mono-' if monochrome else '', size, size)
        # asset_file += '-[%s][%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []), '-'.join(to_str(s).replace('.', '_') for s in direction or []))
        asset_file += '-' + make_filename_hash(key)
        asset_file += '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = make_data_url('image/png', contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)


@ns.declare
def grid_image(left_gutter, width, right_gutter, height, columns=1, grid_color=None, baseline_color=None, background_color=None, inline=False):
    if not Image:
        raise SassMissingDependency('PIL', 'image manipulation')
    if grid_color is None:
        grid_color = (120, 170, 250, 15)
    else:
        c = Color(grid_color).value
        grid_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if baseline_color is None:
        baseline_color = (120, 170, 250, 30)
    else:
        c = Color(baseline_color).value
        baseline_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if background_color is None:
        background_color = (0, 0, 0, 0)
    else:
        c = Color(background_color).value
        background_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    _height = int(height) if height >= 1 else int(height * 1000.0)
    _width = int(width) if width >= 1 else int(width * 1000.0)
    _left_gutter = int(left_gutter) if left_gutter >= 1 else int(left_gutter * 1000.0)
    _right_gutter = int(right_gutter) if right_gutter >= 1 else int(right_gutter * 1000.0)
    if _height <= 0 or _width <= 0 or _left_gutter <= 0 or _right_gutter <= 0:
        raise ValueError
    _full_width = (_left_gutter + _width + _right_gutter)
    new_image = Image.new(
        mode='RGBA',
        size=(_full_width * int(columns), _height),
        color=background_color
    )
    draw = ImageDraw.Draw(new_image)
    for i in range(int(columns)):
        draw.rectangle((i * _full_width + _left_gutter, 0, i * _full_width + _left_gutter + _width - 1, _height - 1),  fill=grid_color)
    if _height > 1:
        draw.rectangle((0, _height - 1, _full_width * int(columns) - 1, _height - 1),  fill=baseline_color)
    if not inline:
        grid_name = 'grid_'
        if left_gutter:
            grid_name += str(int(left_gutter)) + '+'
        grid_name += str(int(width))
        if right_gutter:
            grid_name += '+' + str(int(right_gutter))
        if height and height > 1:
            grid_name += 'x' + str(int(height))
        key = (columns, grid_color, baseline_color, background_color)
        key = grid_name + '-' + make_filename_hash(key)
        asset_file = key + '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = make_data_url('image/png', contents)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)


@ns.declare
def image_color(color, width=1, height=1):
    if not Image:
        raise SassMissingDependency('PIL', 'image manipulation')
    w = int(Number(width).value)
    h = int(Number(height).value)
    if w <= 0 or h <= 0:
        raise ValueError
    new_image = Image.new(
        mode='RGB' if color.alpha == 1 else 'RGBA',
        size=(w, h),
        color=color.rgba255,
    )
    output = six.BytesIO()
    new_image.save(output, format='PNG')
    contents = output.getvalue()
    output.close()
    url = make_data_url('image/png', contents)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
