from __future__ import print_function, division

try:
    from ctypes import c_float, c_int, c_double
except ImportError:
    pass

import pyglet.gl as pgl
from sympy.core import S
from sympy.core.compatibility import range, string_types


def get_model_matrix(array_type=c_float, glGetMethod=pgl.glGetFloatv):
    """
    Returns the current modelview matrix.
    """
    m = (array_type*16)()
    glGetMethod(pgl.GL_MODELVIEW_MATRIX, m)
    return m


def get_projection_matrix(array_type=c_float, glGetMethod=pgl.glGetFloatv):
    """
    Returns the current modelview matrix.
    """
    m = (array_type*16)()
    glGetMethod(pgl.GL_PROJECTION_MATRIX, m)
    return m


def get_viewport():
    """
    Returns the current viewport.
    """
    m = (c_int*4)()
    pgl.glGetIntegerv(pgl.GL_VIEWPORT, m)
    return m


def get_direction_vectors():
    m = get_model_matrix()
    return ((m[0], m[4], m[8]),
            (m[1], m[5], m[9]),
            (m[2], m[6], m[10]))


def get_view_direction_vectors():
    m = get_model_matrix()
    return ((m[0], m[1], m[2]),
            (m[4], m[5], m[6]),
            (m[8], m[9], m[10]))


def get_basis_vectors():
    return ((1, 0, 0), (0, 1, 0), (0, 0, 1))


def screen_to_model(x, y, z):
    m = get_model_matrix(c_double, pgl.glGetDoublev)
    p = get_projection_matrix(c_double, pgl.glGetDoublev)
    w = get_viewport()
    mx, my, mz = c_double(), c_double(), c_double()
    pgl.gluUnProject(x, y, z, m, p, w, mx, my, mz)
    return float(mx.value), float(my.value), float(mz.value)


def model_to_screen(x, y, z):
    m = get_model_matrix(c_double, pgl.glGetDoublev)
    p = get_projection_matrix(c_double, pgl.glGetDoublev)
    w = get_viewport()
    mx, my, mz = c_double(), c_double(), c_double()
    pgl.gluProject(x, y, z, m, p, w, mx, my, mz)
    return float(mx.value), float(my.value), float(mz.value)


def vec_subs(a, b):
    return tuple(a[i] - b[i] for i in range(len(a)))


def billboard_matrix():
    """
    Removes rotational components of
    current matrix so that primitives
    are always drawn facing the viewer.

    |1|0|0|x|
    |0|1|0|x|
    |0|0|1|x| (x means left unchanged)
    |x|x|x|x|
    """
    m = get_model_matrix()
    # XXX: for i in range(11): m[i] = i ?
    m[0] = 1
    m[1] = 0
    m[2] = 0
    m[4] = 0
    m[5] = 1
    m[6] = 0
    m[8] = 0
    m[9] = 0
    m[10] = 1
    pgl.glLoadMatrixf(m)


def create_bounds():
    return [[S.Infinity, -S.Infinity, 0],
            [S.Infinity, -S.Infinity, 0],
            [S.Infinity, -S.Infinity, 0]]


def update_bounds(b, v):
    if v is None:
        return
    for axis in range(3):
        b[axis][0] = min([b[axis][0], v[axis]])
        b[axis][1] = max([b[axis][1], v[axis]])


def interpolate(a_min, a_max, a_ratio):
    return a_min + a_ratio * (a_max - a_min)


def rinterpolate(a_min, a_max, a_value):
    a_range = a_max - a_min
    if a_max == a_min:
        a_range = 1.0
    return (a_value - a_min) / float(a_range)


def interpolate_color(color1, color2, ratio):
    return tuple(interpolate(color1[i], color2[i], ratio) for i in range(3))


def scale_value(v, v_min, v_len):
    return (v - v_min) / v_len


def scale_value_list(flist):
    v_min, v_max = min(flist), max(flist)
    v_len = v_max - v_min
    return list(scale_value(f, v_min, v_len) for f in flist)


def strided_range(r_min, r_max, stride, max_steps=50):
    o_min, o_max = r_min, r_max
    if abs(r_min - r_max) < 0.001:
        return []
    try:
        range(int(r_min - r_max))
    except (TypeError, OverflowError):
        return []
    if r_min > r_max:
        raise ValueError("r_min can not be greater than r_max")
    r_min_s = (r_min % stride)
    r_max_s = stride - (r_max % stride)
    if abs(r_max_s - stride) < 0.001:
        r_max_s = 0.0
    r_min -= r_min_s
    r_max += r_max_s
    r_steps = int((r_max - r_min)/stride)
    if max_steps and r_steps > max_steps:
        return strided_range(o_min, o_max, stride*2)
    return [r_min] + list(r_min + e*stride for e in range(1, r_steps + 1)) + [r_max]


def parse_option_string(s):
    if not isinstance(s, string_types):
        return None
    options = {}
    for token in s.split(';'):
        pieces = token.split('=')
        if len(pieces) == 1:
            option, value = pieces[0], ""
        elif len(pieces) == 2:
            option, value = pieces
        else:
            raise ValueError("Plot option string '%s' is malformed." % (s))
        options[option.strip()] = value.strip()
    return options


def dot_product(v1, v2):
    return sum(v1[i]*v2[i] for i in range(3))


def vec_sub(v1, v2):
    return tuple(v1[i] - v2[i] for i in range(3))


def vec_mag(v):
    return sum(v[i]**2 for i in range(3))**(0.5)
