from collections import OrderedDict
import numpy as np

from ..base import (
    validate_input, connectivity_from_array, pcloud_and_lgroup_from_ranges,
    connectivity_from_range, labeller_func)


@labeller_func(group_label='face_ibug_68')
def face_ibug_68_to_face_ibug_68(pcloud):
    r"""
    Apply the IBUG 68-point semantic labels.

    The semantic labels are as follows:

      - jaw
      - left_eyebrow
      - right_eyebrow
      - nose
      - left_eye
      - right_eye
      - mouth

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 68
    validate_input(pcloud, n_expected_points)

    jaw_indices = np.arange(0, 17)
    lbrow_indices = np.arange(17, 22)
    rbrow_indices = np.arange(22, 27)
    upper_nose_indices = np.arange(27, 31)
    lower_nose_indices = np.arange(31, 36)
    leye_indices = np.arange(36, 42)
    reye_indices = np.arange(42, 48)
    outer_mouth_indices = np.arange(48, 60)
    inner_mouth_indices = np.arange(60, 68)

    jaw_connectivity = connectivity_from_array(jaw_indices)
    lbrow_connectivity = connectivity_from_array(lbrow_indices)
    rbrow_connectivity = connectivity_from_array(rbrow_indices)
    nose_connectivity = np.vstack([
        connectivity_from_array(upper_nose_indices),
        connectivity_from_array(lower_nose_indices)])
    leye_connectivity = connectivity_from_array(leye_indices, close_loop=True)
    reye_connectivity = connectivity_from_array(reye_indices, close_loop=True)
    mouth_connectivity = np.vstack([
        connectivity_from_array(outer_mouth_indices, close_loop=True),
        connectivity_from_array(inner_mouth_indices, close_loop=True)])

    all_connectivity = np.vstack([
        jaw_connectivity, lbrow_connectivity, rbrow_connectivity,
        nose_connectivity, leye_connectivity, reye_connectivity,
        mouth_connectivity
    ])

    mapping = OrderedDict()
    mapping['jaw'] = jaw_indices
    mapping['left_eyebrow'] = lbrow_indices
    mapping['right_eyebrow'] = rbrow_indices
    mapping['nose'] = np.hstack((upper_nose_indices, lower_nose_indices))
    mapping['left_eye'] = leye_indices
    mapping['right_eye'] = reye_indices
    mapping['mouth'] = np.hstack((outer_mouth_indices, inner_mouth_indices))

    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points, all_connectivity, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_68')
def face_ibug_68_mirrored_to_face_ibug_68(pcloud):
    r"""
    Apply the IBUG 68-point semantic labels, on a pointcloud that has been
    mirrored around the vertical axis (flipped around the Y-axis). Thus, on
    the flipped image the jaw etc would be the wrong way around. This
    rectifies that and returns a new PointCloud whereby all the points
    are oriented correctly.

    The semantic labels applied are as follows:

      - jaw
      - left_eyebrow
      - right_eyebrow
      - nose
      - left_eye
      - right_eye
      - mouth

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    new_pcloud, old_map = face_ibug_68_to_face_ibug_68(pcloud,
                                                       return_mapping=True)
    lms_map = np.hstack([old_map['jaw'][::-1],
                         old_map['right_eyebrow'][::-1],
                         old_map['left_eyebrow'][::-1],
                         old_map['nose'][:4],
                         old_map['nose'][4:][::-1],
                         np.roll(old_map['right_eye'][::-1], 4),
                         np.roll(old_map['left_eye'][::-1], 4),
                         np.roll(old_map['mouth'][:12][::-1], 7),
                         np.roll(old_map['mouth'][12:][::-1], 5)])
    return new_pcloud.from_vector(pcloud.points[lms_map]), old_map


@labeller_func(group_label='face_ibug_66')
def face_ibug_68_to_face_ibug_66(pcloud):
    r"""
    Apply the IBUG 66-point semantic labels, but ignoring the 2 points
    describing the inner mouth corners).

    The semantic labels applied are as follows:

      - jaw
      - left_eyebrow
      - right_eyebrow
      - nose
      - left_eye
      - right_eye
      - mouth

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 68
    validate_input(pcloud, n_expected_points)

    jaw_indices = np.arange(0, 17)
    lbrow_indices = np.arange(17, 22)
    rbrow_indices = np.arange(22, 27)
    upper_nose_indices = np.arange(27, 31)
    lower_nose_indices = np.arange(31, 36)
    leye_indices = np.arange(36, 42)
    reye_indices = np.arange(42, 48)
    outer_mouth_indices = np.arange(48, 60)
    inner_mouth_indices = np.hstack((48, np.arange(60, 63),
                                     54, np.arange(63, 66)))

    jaw_connectivity = connectivity_from_array(jaw_indices)
    lbrow_connectivity = connectivity_from_array(lbrow_indices)
    rbrow_connectivity = connectivity_from_array(rbrow_indices)
    nose_connectivity = np.vstack([
        connectivity_from_array(upper_nose_indices),
        connectivity_from_array(lower_nose_indices)])
    leye_connectivity = connectivity_from_array(leye_indices, close_loop=True)
    reye_connectivity = connectivity_from_array(reye_indices, close_loop=True)
    mouth_connectivity = np.vstack([
        connectivity_from_array(outer_mouth_indices, close_loop=True),
        connectivity_from_array(inner_mouth_indices, close_loop=True)])

    all_connectivity = np.vstack([
        jaw_connectivity, lbrow_connectivity, rbrow_connectivity,
        nose_connectivity, leye_connectivity, reye_connectivity,
        mouth_connectivity])

    mapping = OrderedDict()
    mapping['jaw'] = jaw_indices
    mapping['left_eyebrow'] = lbrow_indices
    mapping['right_eyebrow'] = rbrow_indices
    mapping['nose'] = np.hstack([upper_nose_indices, lower_nose_indices])
    mapping['left_eye'] = leye_indices
    mapping['right_eye'] = reye_indices
    mapping['mouth'] = np.hstack([outer_mouth_indices, inner_mouth_indices])

    # Ignore the two inner mouth points
    ind = np.hstack((np.arange(60), np.arange(61, 64), np.arange(65, 68)))
    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points[ind], all_connectivity, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_51')
def face_ibug_68_to_face_ibug_51(pcloud):
    r"""
    Apply the IBUG 51-point semantic labels, but removing the annotations
    corresponding to the jaw region.

    The semantic labels applied are as follows:

      - left_eyebrow
      - right_eyebrow
      - nose
      - left_eye
      - right_eye
      - mouth

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 68
    validate_input(pcloud, n_expected_points)

    lbrow_indices = np.arange(0, 5)
    rbrow_indices = np.arange(5, 10)
    upper_nose_indices = np.arange(10, 14)
    lower_nose_indices = np.arange(14, 19)
    leye_indices = np.arange(19, 25)
    reye_indices = np.arange(25, 31)
    outer_mouth_indices = np.arange(31, 43)
    inner_mouth_indices = np.arange(43, 51)

    lbrow_connectivity = connectivity_from_array(lbrow_indices)
    rbrow_connectivity = connectivity_from_array(rbrow_indices)
    nose_connectivity = np.vstack([
        connectivity_from_array(upper_nose_indices),
        connectivity_from_array(lower_nose_indices)])
    leye_connectivity = connectivity_from_array(leye_indices, close_loop=True)
    reye_connectivity = connectivity_from_array(reye_indices, close_loop=True)
    mouth_connectivity = np.vstack([
        connectivity_from_array(outer_mouth_indices, close_loop=True),
        connectivity_from_array(inner_mouth_indices, close_loop=True)])

    all_connectivity = np.vstack([
        lbrow_connectivity, rbrow_connectivity, nose_connectivity,
        leye_connectivity, reye_connectivity, mouth_connectivity])

    mapping = OrderedDict()
    mapping['left_eyebrow'] = lbrow_indices
    mapping['right_eyebrow'] = rbrow_indices
    mapping['nose'] = np.hstack([upper_nose_indices, lower_nose_indices])
    mapping['left_eye'] = leye_indices
    mapping['right_eye'] = reye_indices
    mapping['mouth'] = np.hstack([outer_mouth_indices, inner_mouth_indices])

    # Ignore the two inner mouth points
    ind = np.arange(17, 68)
    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points[ind], all_connectivity, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_49')
def face_ibug_49_to_face_ibug_49(pcloud):
    r"""
    Apply the IBUG 49-point semantic labels.

    The semantic labels applied are as follows:

      - left_eyebrow
      - right_eyebrow
      - nose
      - left_eye
      - right_eye
      - mouth

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 49
    validate_input(pcloud, n_expected_points)

    lbrow_indices = np.arange(0, 5)
    rbrow_indices = np.arange(5, 10)
    upper_nose_indices = np.arange(10, 14)
    lower_nose_indices = np.arange(14, 19)
    leye_indices = np.arange(19, 25)
    reye_indices = np.arange(25, 31)
    outer_mouth_indices = np.arange(31, 43)
    inner_mouth_indices = np.hstack((31, np.arange(43, 46),
                                     37, np.arange(46, 49)))

    lbrow_connectivity = connectivity_from_array(lbrow_indices)
    rbrow_connectivity = connectivity_from_array(rbrow_indices)
    nose_connectivity = np.vstack([
        connectivity_from_array(upper_nose_indices),
        connectivity_from_array(lower_nose_indices)])
    leye_connectivity = connectivity_from_array(leye_indices, close_loop=True)
    reye_connectivity = connectivity_from_array(reye_indices, close_loop=True)
    mouth_connectivity = np.vstack([
        connectivity_from_array(outer_mouth_indices, close_loop=True),
        connectivity_from_array(inner_mouth_indices, close_loop=True)])

    all_connectivity = np.vstack([
        lbrow_connectivity, rbrow_connectivity, nose_connectivity,
        leye_connectivity, reye_connectivity, mouth_connectivity])

    mapping = OrderedDict()
    mapping['left_eyebrow'] = lbrow_indices
    mapping['right_eyebrow'] = rbrow_indices
    mapping['nose'] = np.hstack([upper_nose_indices, lower_nose_indices])
    mapping['left_eye'] = leye_indices
    mapping['right_eye'] = reye_indices
    mapping['mouth'] = np.hstack([outer_mouth_indices, inner_mouth_indices])

    # Ignore the two inner mouth points
    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points, all_connectivity, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_49')
def face_ibug_68_to_face_ibug_49(pcloud):
    r"""
    Apply the IBUG 49-point semantic labels, but removing the annotations
    corresponding to the jaw region and the 2 describing the inner mouth
    corners.

    The semantic labels applied are as follows:

      - left_eyebrow
      - right_eyebrow
      - nose
      - left_eye
      - right_eye
      - mouth

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 68
    validate_input(pcloud, n_expected_points)

    lbrow_indices = np.arange(0, 5)
    rbrow_indices = np.arange(5, 10)
    upper_nose_indices = np.arange(10, 14)
    lower_nose_indices = np.arange(14, 19)
    leye_indices = np.arange(19, 25)
    reye_indices = np.arange(25, 31)
    outer_mouth_indices = np.arange(31, 43)
    inner_mouth_indices = np.hstack((31, np.arange(43, 46),
                                     37, np.arange(46, 49)))

    lbrow_connectivity = connectivity_from_array(lbrow_indices)
    rbrow_connectivity = connectivity_from_array(rbrow_indices)
    nose_connectivity = np.vstack([
        connectivity_from_array(upper_nose_indices),
        connectivity_from_array(lower_nose_indices)])
    leye_connectivity = connectivity_from_array(leye_indices, close_loop=True)
    reye_connectivity = connectivity_from_array(reye_indices, close_loop=True)
    mouth_connectivity = np.vstack([
        connectivity_from_array(outer_mouth_indices, close_loop=True),
        connectivity_from_array(inner_mouth_indices, close_loop=True)])

    all_connectivity = np.vstack([
        lbrow_connectivity, rbrow_connectivity, nose_connectivity,
        leye_connectivity, reye_connectivity, mouth_connectivity])

    mapping = OrderedDict()
    mapping['left_eyebrow'] = lbrow_indices
    mapping['right_eyebrow'] = rbrow_indices
    mapping['nose'] = np.hstack([upper_nose_indices, lower_nose_indices])
    mapping['left_eye'] = leye_indices
    mapping['right_eye'] = reye_indices
    mapping['mouth'] = np.hstack([outer_mouth_indices, inner_mouth_indices])

    # Ignore the two inner mouth points
    ind = np.hstack((np.arange(17, 60), np.arange(61, 64), np.arange(65, 68)))
    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points[ind], all_connectivity, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_68_trimesh')
def face_ibug_68_to_face_ibug_68_trimesh(pcloud):
    r"""
    Apply the IBUG 68-point semantic labels, with trimesh connectivity.

    The semantic labels applied are as follows:

      - tri

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import TriMesh

    n_expected_points = 68
    validate_input(pcloud, n_expected_points)

    tri_list = np.array([[47, 29, 28], [44, 43, 23], [38, 20, 21],
                         [47, 28, 42], [49, 61, 60], [40, 41, 37],
                         [37, 19, 20], [28, 40, 39], [38, 21, 39],
                         [36,  1,  0], [48, 59,  4], [49, 60, 48],
                         [67, 59, 60], [13, 53, 14], [61, 51, 62],
                         [57,  8,  7], [52, 51, 33], [61, 67, 60],
                         [52, 63, 51], [66, 56, 57], [35, 30, 29],
                         [53, 52, 35], [37, 36, 17], [18, 37, 17],
                         [37, 38, 40], [38, 37, 20], [19, 37, 18],
                         [38, 39, 40], [28, 29, 40], [41, 36, 37],
                         [27, 39, 21], [41, 31,  1], [30, 32, 31],
                         [33, 51, 50], [33, 30, 34], [31, 40, 29],
                         [36,  0, 17], [31,  2,  1], [31, 41, 40],
                         [ 1, 36, 41], [31, 49,  2], [ 2, 49,  3],
                         [60, 59, 48], [ 3, 49, 48], [31, 32, 50],
                         [48,  4,  3], [59,  5,  4], [58, 67, 66],
                         [ 5, 59, 58], [58, 59, 67], [ 7,  6, 58],
                         [66, 57, 58], [13, 54, 53], [ 7, 58, 57],
                         [ 6,  5, 58], [50, 61, 49], [62, 67, 61],
                         [31, 50, 49], [32, 33, 50], [30, 33, 32],
                         [34, 52, 33], [35, 52, 34], [53, 63, 52],
                         [62, 63, 65], [62, 51, 63], [66, 65, 56],
                         [63, 53, 64], [62, 66, 67], [62, 65, 66],
                         [57, 56,  9], [65, 63, 64], [ 8, 57,  9],
                         [ 9, 56, 10], [10, 56, 11], [11, 56, 55],
                         [11, 55, 12], [56, 65, 55], [55, 64, 54],
                         [55, 65, 64], [55, 54, 12], [64, 53, 54],
                         [12, 54, 13], [45, 46, 44], [35, 34, 30],
                         [14, 53, 35], [15, 46, 45], [27, 28, 39],
                         [27, 42, 28], [35, 29, 47], [30, 31, 29],
                         [15, 35, 46], [15, 14, 35], [43, 22, 23],
                         [27, 21, 22], [24, 44, 23], [44, 47, 43],
                         [43, 47, 42], [46, 35, 47], [26, 45, 44],
                         [46, 47, 44], [25, 44, 24], [25, 26, 44],
                         [16, 15, 45], [16, 45, 26], [22, 42, 43],
                         [50, 51, 61], [27, 22, 42]])
    new_pcloud = TriMesh(pcloud.points, trilist=tri_list)

    mapping = OrderedDict()
    mapping['tri'] = np.arange(new_pcloud.n_points)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_66_trimesh')
def face_ibug_68_to_face_ibug_66_trimesh(pcloud):
    r"""
    Apply the IBUG 66-point semantic labels, with trimesh connectivity.

    The semantic labels applied are as follows:

      - tri

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import TriMesh

    # Apply face_ibug_68_to_face_ibug_66
    new_pcloud = face_ibug_68_to_face_ibug_66(pcloud)

    # This is in terms of the 66 points
    tri_list = np.array([[47, 29, 28], [44, 43, 23], [38, 20, 21],
                         [47, 28, 42], [40, 41, 37], [51, 62, 61],
                         [37, 19, 20], [28, 40, 39], [38, 21, 39],
                         [36,  1,  0],  [48, 59, 4], [49, 60, 48],
                         [13, 53, 14], [60, 51, 61], [51, 51, 62],
                         [52, 51, 33], [49, 50, 60], [57,  7,  8],
                         [64, 56, 57], [35, 30, 29], [52, 62, 53],
                         [53, 52, 35], [37, 36, 17], [18, 37, 17],
                         [37, 38, 40], [38, 37, 20], [19, 37, 18],
                         [38, 39, 40], [28, 29, 40], [41, 36, 37],
                         [27, 39, 21], [41, 31,  1], [30, 32, 31],
                         [33, 51, 50], [33, 30, 34], [31, 40, 29],
                         [36,  0, 17], [31,  2,  1], [31, 41, 40],
                         [ 1, 36, 41], [31, 49,  2], [ 2, 49,  3],
                         [ 3, 49, 48], [31, 32, 50], [62, 53, 54],
                         [48,  4,  3], [59,  5,  4], [58, 65, 64],
                         [ 5, 59, 58], [58, 59, 65], [ 7,  6, 58],
                         [64, 57, 58], [13, 54, 53], [ 7, 58, 57],
                         [ 6,  5, 58], [63, 55, 54], [65, 59, 48],
                         [31, 50, 49], [32, 33, 50], [30, 33, 32],
                         [34, 52, 33], [35, 52, 34], [48, 60, 65],
                         [64, 63, 56], [60, 65, 61], [65, 64, 61],
                         [57, 56,  9], [ 8, 57,  9], [64, 63, 61],
                         [ 9, 56, 10], [10, 56, 11], [11, 56, 55],
                         [11, 55, 12], [56, 63, 55], [51, 52, 62],
                         [55, 54, 12], [63, 54, 62], [61, 62, 63],
                         [12, 54, 13], [45, 46, 44], [35, 34, 30],
                         [14, 53, 35], [15, 46, 45], [27, 28, 39],
                         [27, 42, 28], [35, 29, 47], [30, 31, 29],
                         [15, 35, 46], [15, 14, 35], [43, 22, 23],
                         [27, 21, 22], [24, 44, 23], [44, 47, 43],
                         [43, 47, 42], [46, 35, 47], [26, 45, 44],
                         [46, 47, 44], [25, 44, 24], [25, 26, 44],
                         [16, 15, 45], [16, 45, 26], [22, 42, 43],
                         [50, 60, 51], [27, 22, 42]])

    new_pcloud = TriMesh(new_pcloud.points, trilist=tri_list, copy=False)

    mapping = OrderedDict()
    mapping['tri'] = np.arange(new_pcloud.n_points)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_51_trimesh')
def face_ibug_68_to_face_ibug_51_trimesh(pcloud):
    r"""
    Apply the IBUG 51-point semantic labels, with trimesh connectivity..

    The semantic labels applied are as follows:

      - tri

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import TriMesh

    # Apply face_ibug_68_to_face_ibug_51
    new_pcloud = face_ibug_68_to_face_ibug_51(pcloud)

    # This is in terms of the 51 points
    tri_list = np.array([[30, 12, 11], [27, 26,  6], [21,  3,  4],
                         [30, 11, 25], [32, 44, 43], [23, 24, 20],
                         [20,  2,  3], [11, 23, 22], [21,  4, 22],
                         [32, 43, 31], [50, 42, 43], [44, 34, 45],
                         [35, 34, 16], [44, 50, 43], [35, 46, 34],
                         [49, 39, 40], [18, 13, 12], [36, 35, 18],
                         [20, 19,  0], [ 1, 20,  0], [20, 21, 23],
                         [21, 20,  3], [ 2, 20,  1], [21, 22, 23],
                         [11, 12, 23], [24, 19, 20], [10, 22,  4],
                         [13, 15, 14], [16, 34, 33], [16, 13, 17],
                         [14, 23, 12], [14, 24, 23], [43, 42, 31],
                         [14, 15, 33], [41, 50, 49], [41, 42, 50],
                         [49, 40, 41], [33, 44, 32], [45, 50, 44],
                         [14, 33, 32], [15, 16, 33], [13, 16, 15],
                         [17, 35, 16], [18, 35, 17], [36, 46, 35],
                         [45, 46, 48], [45, 34, 46], [49, 48, 39],
                         [46, 36, 47], [45, 49, 50], [45, 48, 49],
                         [48, 46, 47], [39, 48, 38], [38, 47, 37],
                         [38, 48, 47], [47, 36, 37], [28, 29, 27],
                         [18, 17, 13], [10, 11, 22], [10, 25, 11],
                         [18, 12, 30], [13, 14, 12], [26,  5,  6],
                         [10,  4,  5], [ 7, 27,  6], [27, 30, 26],
                         [26, 30, 25], [29, 18, 30], [ 9, 28, 27],
                         [29, 30, 27], [ 8, 27,  7], [ 8,  9, 27],
                         [ 5, 25, 26], [33, 34, 44], [10,  5, 25]])

    new_pcloud = TriMesh(new_pcloud.points, trilist=tri_list, copy=False)

    mapping = OrderedDict()
    mapping['tri'] = np.arange(new_pcloud.n_points)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_49_trimesh')
def face_ibug_68_to_face_ibug_49_trimesh(pcloud):
    r"""
    Apply the IBUG 49-point semantic labels, with trimesh connectivity.

    The semantic labels applied are as follows:

      - tri

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import TriMesh

    # Apply face_ibug_68_to_face_ibug_49
    new_pcloud = face_ibug_68_to_face_ibug_49(pcloud)

    # This is in terms of the 49 points
    tri_list = np.array([[47, 29, 28], [44, 43, 23], [38, 20, 21],
                         [47, 28, 42], [40, 41, 37], [51, 62, 61],
                         [37, 19, 20], [28, 40, 39], [38, 21, 39],
                         [36,  1,  0],  [48, 59, 4], [49, 60, 48],
                         [13, 53, 14], [60, 51, 61], [51, 51, 62],
                         [52, 51, 33], [49, 50, 60], [57,  7,  8],
                         [64, 56, 57], [35, 30, 29], [52, 62, 53],
                         [53, 52, 35], [37, 36, 17], [18, 37, 17],
                         [37, 38, 40], [38, 37, 20], [19, 37, 18],
                         [38, 39, 40], [28, 29, 40], [41, 36, 37],
                         [27, 39, 21], [41, 31,  1], [30, 32, 31],
                         [33, 51, 50], [33, 30, 34], [31, 40, 29],
                         [36,  0, 17], [31,  2,  1], [31, 41, 40],
                         [ 1, 36, 41], [31, 49,  2], [ 2, 49,  3],
                         [ 3, 49, 48], [31, 32, 50], [62, 53, 54],
                         [48,  4,  3], [59,  5,  4], [58, 65, 64],
                         [ 5, 59, 58], [58, 59, 65], [ 7,  6, 58],
                         [64, 57, 58], [13, 54, 53], [ 7, 58, 57],
                         [ 6,  5, 58], [63, 55, 54], [65, 59, 48],
                         [31, 50, 49], [32, 33, 50], [30, 33, 32],
                         [34, 52, 33], [35, 52, 34], [48, 60, 65],
                         [64, 63, 56], [60, 65, 61], [65, 64, 61],
                         [57, 56,  9], [ 8, 57,  9], [64, 63, 61],
                         [ 9, 56, 10], [10, 56, 11], [11, 56, 55],
                         [11, 55, 12], [56, 63, 55], [51, 52, 62],
                         [55, 54, 12], [63, 54, 62], [61, 62, 63],
                         [12, 54, 13], [45, 46, 44], [35, 34, 30],
                         [14, 53, 35], [15, 46, 45], [27, 28, 39],
                         [27, 42, 28], [35, 29, 47], [30, 31, 29],
                         [15, 35, 46], [15, 14, 35], [43, 22, 23],
                         [27, 21, 22], [24, 44, 23], [44, 47, 43],
                         [43, 47, 42], [46, 35, 47], [26, 45, 44],
                         [46, 47, 44], [25, 44, 24], [25, 26, 44],
                         [16, 15, 45], [16, 45, 26], [22, 42, 43],
                         [50, 60, 51], [27, 22, 42]])

    new_pcloud = TriMesh(new_pcloud.points, trilist=tri_list, copy=False)

    mapping = OrderedDict()
    mapping['tri'] = np.arange(new_pcloud.n_points)

    return new_pcloud, mapping


@labeller_func(group_label='face_ibug_65')
def face_ibug_68_to_face_ibug_65(pcloud):
    r"""
    Apply the IBUG 68 point semantic labels, but ignore the 3 points that are
    coincident for a closed mouth (bottom of the inner mouth).

    The semantic labels applied are as follows:

      - jaw
      - left_eyebrow
      - right_eyebrow
      - nose
      - left_eye
      - right_eye
      - mouth

    References
    ----------
    .. [1] http://www.multipie.org/
    .. [2] http://ibug.doc.ic.ac.uk/resources/300-W/
    """
    from menpo.shape import LabelledPointUndirectedGraph

    # Apply face_ibug_68_to_face_ibug_68
    new_pcloud, mapping = face_ibug_68_to_face_ibug_68(pcloud,
                                                       return_mapping=True)

    # The coincident points are considered the final 3 landmarks (bottom of
    # the inner mouth points). We remove all the edges for the inner mouth
    # which are the last 8.
    edges = new_pcloud.edges[:-8]
    # Re-add the inner mouth without the bottom 3 points
    edges = np.vstack([edges,
                       connectivity_from_range((60, 65), close_loop=True)])

    # Luckily, OrderedDict maintains the original ordering despite updates
    outer_mouth_indices = np.arange(48, 60)
    inner_mouth_indices = np.arange(60, 65)
    mapping['mouth'] = np.hstack([outer_mouth_indices, inner_mouth_indices])

    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        new_pcloud.points[:-3], edges, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='face_imm_58')
def face_imm_58_to_face_imm_58(pcloud):
    r"""
    Apply the 58-point semantic labels from the IMM dataset.

    The semantic labels applied are as follows:

      - jaw
      - left_eye
      - right_eye
      - left_eyebrow
      - right_eyebrow
      - mouth
      - nose

    References
    ----------
    .. [1] http://www2.imm.dtu.dk/~aam/
    """
    n_expected_points = 58
    validate_input(pcloud, n_expected_points)

    labels = OrderedDict([
        ('jaw', (0, 13, False)),
        ('left_eye', (13, 21, True)),
        ('right_eye', (21, 29, True)),
        ('left _eyebrow', (29, 34, False)),
        ('right_eyebrow', (34, 39, False)),
        ('mouth', (39, 47, True)),
        ('nose', (47, 58, False))
    ])
    return pcloud_and_lgroup_from_ranges(pcloud, labels)


@labeller_func(group_label='face_lfpw_29')
def face_lfpw_29_to_face_lfpw_29(pcloud):
    r"""
    Apply the 29-point semantic labels from the original LFPW dataset.

    The semantic labels applied are as follows:

      - chin
      - left_eye
      - right_eye
      - left_eyebrow
      - right_eyebrow
      - mouth
      - nose

    References
    ----------
    .. [1] http://homes.cs.washington.edu/~neeraj/databases/lfpw/
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 29
    validate_input(pcloud, n_expected_points)

    chin_indices = np.array([28])
    outer_leye_indices = np.array([8, 12, 10, 13])
    pupil_leye_indices = np.array([16])
    outer_reye_indices = np.array([11, 14, 9, 15])
    pupil_reye_indices = np.array([17])
    lbrow_indices = np.array([0, 4, 2, 5])
    rbrow_indices = np.array([3, 6, 1, 7])
    outer_mouth_indices = np.array([22, 24, 23, 27])
    inner_mouth_indices = np.array([22, 25, 23, 26])
    nose_indices = np.array([18, 20, 19, 21])

    chin_connectivity = connectivity_from_array(chin_indices, close_loop=True)
    leye_connectivity = connectivity_from_array(outer_leye_indices,
                                                close_loop=True)
    reye_connectivity = connectivity_from_array(outer_reye_indices,
                                                close_loop=True)
    lbrow_connectivity = connectivity_from_array(lbrow_indices,
                                                 close_loop=True)
    rbrow_connectivity = connectivity_from_array(rbrow_indices,
                                                 close_loop=True)
    mouth_connectivity = np.vstack([
        connectivity_from_array(outer_mouth_indices, close_loop=True),
        connectivity_from_array(inner_mouth_indices, close_loop=True)])
    nose_connectivity = connectivity_from_array(nose_indices, close_loop=True)

    all_connectivity = np.vstack([
        chin_connectivity, leye_connectivity, reye_connectivity,
        lbrow_connectivity, rbrow_connectivity, mouth_connectivity,
        nose_connectivity])

    mapping = OrderedDict()
    mapping['chin'] = chin_indices
    mapping['left_eye'] = np.hstack((outer_leye_indices, pupil_leye_indices))
    mapping['right_eye'] = np.hstack((outer_reye_indices, pupil_reye_indices))
    mapping['left_eyebrow'] = lbrow_indices
    mapping['right_eyebrow'] = rbrow_indices
    mapping['mouth'] = np.hstack((outer_mouth_indices, inner_mouth_indices))
    mapping['nose'] = nose_indices

    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points, all_connectivity, mapping)

    return new_pcloud, mapping


def _build_upper_eyelid():
    top_indices = np.arange(0, 7)
    middle_indices = np.arange(12, 17)
    upper_eyelid_indices = np.hstack((top_indices, middle_indices))

    upper_eyelid_connectivity = list(zip(top_indices, top_indices[1:]))
    upper_eyelid_connectivity += [(0, 12)]
    upper_eyelid_connectivity += list(zip(middle_indices, middle_indices[1:]))
    upper_eyelid_connectivity += [(16, 6)]

    return upper_eyelid_indices, upper_eyelid_connectivity


@labeller_func(group_label='eye_ibug_open_38')
def eye_ibug_open_38_to_eye_ibug_open_38(pcloud):
    r"""
    Apply the IBUG 38-point open eye semantic labels.

    The semantic labels applied are as follows:

      - upper_eyelid
      - lower_eyelid
      - iris
      - pupil
      - sclera
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 38
    validate_input(pcloud, n_expected_points)

    upper_el_indices, upper_el_connectivity = _build_upper_eyelid()

    iris_range = (22, 30)
    pupil_range = (30, 38)
    sclera_top = np.arange(12, 17)
    sclera_bottom = np.arange(17, 22)
    sclera_indices = np.hstack((0, sclera_top, 6, sclera_bottom))
    lower_el_top = np.arange(17, 22)
    lower_el_bottom = np.arange(7, 12)
    lower_el_indices = np.hstack((6, lower_el_top, 0, lower_el_bottom))

    iris_connectivity = connectivity_from_range(iris_range, close_loop=True)
    pupil_connectivity = connectivity_from_range(pupil_range, close_loop=True)

    sclera_connectivity = list(zip(sclera_top, sclera_top[1:]))
    sclera_connectivity += [(0, 21)]
    sclera_connectivity += list(zip(sclera_bottom, sclera_bottom[1:]))
    sclera_connectivity += [(6, 17)]

    lower_el_connectivity = list(zip(lower_el_top, lower_el_top[1:]))
    lower_el_connectivity += [(6, 7)]
    lower_el_connectivity += list(zip(lower_el_bottom, lower_el_bottom[1:]))
    lower_el_connectivity += [(11, 0)]

    all_connectivity = np.asarray(upper_el_connectivity +
                                  lower_el_connectivity +
                                  iris_connectivity.tolist() +
                                  pupil_connectivity.tolist() +
                                  sclera_connectivity)

    mapping = OrderedDict()
    mapping['upper_eyelid'] = upper_el_indices
    mapping['lower_eyelid'] = lower_el_indices
    mapping['pupil'] = np.arange(*pupil_range)
    mapping['iris'] = np.arange(*iris_range)
    mapping['sclera'] = sclera_indices

    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points, all_connectivity, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='eye_ibug_close_17')
def eye_ibug_close_17_to_eye_ibug_close_17(pcloud):
    r"""
    Apply the IBUG 17-point close eye semantic labels.

    The semantic labels applied are as follows:

      - upper_eyelid
      - lower_eyelid
    """
    from menpo.shape import LabelledPointUndirectedGraph

    n_expected_points = 17
    validate_input(pcloud, n_expected_points)

    upper_indices, upper_connectivity = _build_upper_eyelid()

    middle_indices = np.arange(12, 17)
    bottom_indices = np.arange(6, 12)
    lower_indices = np.hstack((bottom_indices, 0, middle_indices))
    lower_connectivity = list(zip(bottom_indices, bottom_indices[1:]))
    lower_connectivity += [(0, 12)]
    lower_connectivity += list(zip(middle_indices, middle_indices[1:]))
    lower_connectivity += [(11, 0)]

    all_connectivity = np.asarray(upper_connectivity + lower_connectivity)

    mapping = OrderedDict()
    mapping['upper_eyelid'] = upper_indices
    mapping['lower_eyelid'] = lower_indices

    new_pcloud = LabelledPointUndirectedGraph.init_from_indices_mapping(
        pcloud.points, all_connectivity, mapping)

    return new_pcloud, mapping


@labeller_func(group_label='eye_ibug_open_38_trimesh')
def eye_ibug_open_38_to_eye_ibug_open_38_trimesh(pcloud):
    r"""
    Apply the IBUG 38-point open eye semantic labels, with trimesh connectivity.

    The semantic labels applied are as follows:

      - tri
    """
    from menpo.shape import TriMesh

    n_expected_points = 38
    validate_input(pcloud, n_expected_points)

    tri_list = np.array([[29, 36, 28], [22, 13, 23], [12,  1,  2],
                         [29, 30, 37], [13,  3, 14], [13, 12,  2],
                         [19,  8,  9], [25, 33, 24], [36, 37, 33],
                         [24, 32, 31], [33, 37, 31], [35, 34, 27],
                         [35, 36, 33], [ 3, 13,  2], [14, 24, 23],
                         [33, 32, 24], [15, 25, 14], [25, 26, 34],
                         [22, 30, 29], [31, 37, 30], [24, 31, 23],
                         [32, 33, 31], [22, 12, 13], [ 0,  1, 12],
                         [14, 23, 13], [31, 30, 23], [28, 19, 20],
                         [21, 11,  0], [12, 21,  0], [20, 11, 21],
                         [20, 10, 11], [21, 29, 20], [21, 12, 22],
                         [30, 22, 23], [29, 21, 22], [27, 19, 28],
                         [29, 37, 36], [29, 28, 20], [36, 35, 28],
                         [20, 19, 10], [10, 19,  9], [28, 35, 27],
                         [19, 19,  8], [17, 16,  6], [18,  7,  8],
                         [25, 34, 33], [18, 27, 17], [18, 19, 27],
                         [18, 17,  7], [27, 26, 17], [17,  6,  7],
                         [14, 25, 24], [34, 35, 33], [17, 26, 16],
                         [27, 34, 26], [ 3, 15, 14], [15, 26, 25],
                         [ 4, 15,  3], [16, 26, 15], [16,  4,  5],
                         [16, 15,  4], [16,  5,  6], [8, 18, 19]])

    new_pcloud = TriMesh(pcloud.points, trilist=tri_list, copy=False)

    mapping = OrderedDict()
    mapping['tri'] = np.arange(new_pcloud.n_points)

    return new_pcloud, mapping


@labeller_func(group_label='eye_ibug_close_17_trimesh')
def eye_ibug_close_17_to_eye_ibug_close_17_trimesh(pcloud):
    r"""
    Apply the IBUG 17-point close eye semantic labels, with trimesh
    connectivity.

    The semantic labels applied are as follows:

      - tri
    """
    from menpo.shape import TriMesh

    n_expected_points = 17
    validate_input(pcloud, n_expected_points)

    tri_list = np.array([[10, 11, 13], [ 3, 13,  2], [ 4, 14,  3],
                         [15,  5, 16], [12, 11,  0], [13, 14, 10],
                         [13, 12,  2], [14, 13,  3], [ 0,  1, 12],
                         [ 2, 12,  1], [13, 11, 12], [ 9, 10, 14],
                         [15,  9, 14], [ 7,  8, 15], [ 5,  6, 16],
                         [15, 14,  4], [ 7, 15, 16], [ 8,  9, 15],
                         [15,  4,  5], [16,  6,  7]])

    new_pcloud = TriMesh(pcloud.points, trilist=tri_list, copy=False)

    mapping = OrderedDict()
    mapping['tri'] = np.arange(new_pcloud.n_points)

    return new_pcloud, mapping


@labeller_func(group_label='tongue_ibug_19')
def tongue_ibug_19_to_tongue_ibug_19(pcloud):
    r"""
    Apply the IBUG 19-point tongue semantic labels.

    The semantic labels applied are as follows:

      - outline
      - bisector
    """
    n_expected_points = 19
    validate_input(pcloud, n_expected_points)

    labels = OrderedDict([
        ('outline', (0, 13, False)),
        ('bisector', (13, 19, False))
    ])
    return pcloud_and_lgroup_from_ranges(pcloud, labels)
