# ----------------------------------------------------------------------------
# cocos2d
# Copyright (c) 2008-2012 Daniel Moisset, Ricardo Quesada, Rayentray Tappa,
# Lucio Torre
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#   * Neither the name of cocos2d nor the names of its
#     contributors may be used to endorse or promote products
#     derived from this software without specific prior written
#     permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
#
# Ideas borrowed from:
#    pygext: http://opioid-interactive.com/~shang/projects/pygext/
#    pyglet astraea: http://www.pyglet.org
#    Grossini's Hell: http://www.pyweek.org/e/Pywiii/
#
"""A `Layer` that implements a simple menu

Menu
====

This module provides a Menu class. Menus can contain regular items
(which trigger a function when selected), toggle items (which toggle a flag when selected),
or entry items (which lets you enter alphanumeric data).

To use a menu in your code, just subclass `Menu` and add the menu to an `Scene` or
another `Layer`.
"""

__docformat__ = 'restructuredtext'

import pyglet
from pyglet import font
from pyglet.window import key
from pyglet.gl import *
import pyglet.graphics

from layer import *
from director import *
from cocosnode import *
from actions import *
from sprite import Sprite
import rect

__all__ = [ 'Menu',                                         # menu class

            'MenuItem', 'ToggleMenuItem',  # menu items classes
            'MultipleMenuItem', 'EntryMenuItem', 'ImageMenuItem',
            'ColorMenuItem',
            'verticalMenuLayout', 'fixedPositionMenuLayout',   # Different menu layou functions
            'CENTER', 'LEFT', 'RIGHT', 'TOP', 'BOTTOM',     # menu aligment

            'shake', 'shake_back','zoom_in','zoom_out'      # Some useful actions for the menu items
            ]

#
# Class Menu
#

# Horizontal Align
CENTER = font.Text.CENTER
LEFT = font.Text.LEFT
RIGHT = font.Text.RIGHT

# Vertical Align
TOP = font.Text.TOP
BOTTOM = font.Text.BOTTOM

def verticalMenuLayout (menu):
    width, height = director.get_window_size()
    fo = font.load(menu.font_item['font_name'], menu.font_item['font_size'])
    fo_height = int( (fo.ascent - fo.descent) * 0.9 )

    if menu.menu_halign == CENTER:
        pos_x = width // 2
    elif menu.menu_halign == RIGHT:
        pos_x = width - menu.menu_hmargin
    elif menu.menu_halign == LEFT:
        pos_x = menu.menu_hmargin
    else:
        raise Exception("Invalid anchor_x value for menu")

    for idx,i in enumerate( menu.children):
        item = i[1]
        if menu.menu_valign == CENTER:
            pos_y = (height + (len(menu.children) - 2 * idx)
                         * fo_height - menu.title_height) * 0.5
        elif menu.menu_valign == TOP:
            pos_y = (height - ((idx + 0.8) * fo_height )
                         - menu.title_height - menu.menu_vmargin)
        elif menu.menu_valign == BOTTOM:
            pos_y = (0 + fo_height * (len(menu.children) - idx) +
                         menu.menu_vmargin)
        item.transform_anchor = (pos_x, pos_y)
        item.generateWidgets (pos_x, pos_y, menu.font_item,
                              menu.font_item_selected)

def fixedPositionMenuLayout (positions):
    def fixedMenuLayout(menu):
        width, height = director.get_window_size()
        for idx,i in enumerate( menu.children):
            item = i[1]
            pos_x = positions[idx][0]
            pos_y = positions[idx][1]
            item.transform_anchor = (pos_x, pos_y)
            item.generateWidgets (pos_x, pos_y, menu.font_item,
                                        menu.font_item_selected)
    return fixedMenuLayout

class Menu(Layer):
    """Abstract base class for menu layers.

    Normal usage is:

     - create a subclass
     - override __init__ to set all style attributes,
       and then call `create_menu()`
     - Finally you shall add the menu to an `Scene` or another `Layer`
    """

    is_event_handler = True #: Receives pyglet events

    select_sound = None
    activate_sound = None
    def __init__( self, title = ''):
        super(Menu, self).__init__()
        #
        # Items and Title
        #
        self.title = title
        self.title_text = None

        self.menu_halign = CENTER
        self.menu_valign = CENTER

        self.menu_hmargin = 2 # Variable margins for left and right alignment
        self.menu_vmargin = 2 # Variable margins for top and bottom alignment

        #
        # Menu default options
        # Menus can be customized changing these variables
        #

        # Title
        self.font_title = {
            'text':'title',
            'font_name':'Arial',
            'font_size':56,
            'color':(192,192,192,255),
            'bold':False,
            'italic':False,
            'anchor_y':'center',
            'anchor_x':'center',
            'dpi':96,
            'x':0, 'y':0,
        }

        self.font_item= {
            'font_name':'Arial',
            'font_size':32,
            'bold':False,
            'italic':False,
            'anchor_y':'center',
            'anchor_x':'center',
            'color':(192,192,192,255),
            'dpi':96,
        }
        self.font_item_selected = {
            'font_name':'Arial',
            'font_size':42,
            'bold':False,
            'italic':False,
            'anchor_y':'center',
            'anchor_x':'center',
            'color':(255,255,255,255),
            'dpi':96,
        }

        self.title_height = 0


    def _generate_title( self ):
        width, height = director.get_window_size()

        self.font_title['x'] = width // 2
        self.font_title['text'] = self.title
        self.title_label = pyglet.text.Label( **self.font_title )
        self.title_label.y = height - self.title_label.content_height //2

        fo = font.load( self.font_title['font_name'], self.font_title['font_size'] )
        self.title_height = self.title_label.content_height

    def _build_items(self, layout_strategy):
        self.font_item_selected['anchor_x'] = self.menu_halign
        self.font_item_selected['anchor_y'] = 'center'

        self.font_item['anchor_x'] = self.menu_halign
        self.font_item['anchor_y'] = 'center'

        layout_strategy(self)
        self.selected_index = 0
        self.children[ self.selected_index ][1].is_selected = True

    def _select_item(self, new_idx):
        if new_idx == self.selected_index:
            return

        if self.select_sound:
            self.select_sound.play()

        self.children[ self.selected_index][1].is_selected = False
        self.children[ self.selected_index][1].on_unselected()

        self.children[ new_idx ][1].is_selected = True
        self.children[ new_idx ][1].on_selected()

        self.selected_index = new_idx

    def _activate_item( self ):
        if self.activate_sound:
            self.activate_sound.play()
        self.children[ self.selected_index][1].on_activated()
        self.children[ self.selected_index ][1].on_key_press( key.ENTER, 0 )

    def create_menu(self, items, selected_effect=None, unselected_effect=None,
                    activated_effect=None, layout_strategy=verticalMenuLayout):
        """Creates the menu

        The order of the list important since the
        first one will be shown first.

        Example::

            l = []
            l.append( MenuItem('Options', self.on_new_game ) )
            l.append( MenuItem('Quit', self.on_quit ) )
            self.create_menu( l, zoom_in(), zoom_out() )

        :Parameters:
            `items` : list
                list of `BaseMenuItem` that will be part of the `Menu`
            `selected_effect` : function
                This action will be executed when the `BaseMenuItem` is selected
            `unselected_effect` : function
                This action will be executed when the `BaseMenuItem` is unselected
            `activated_effect` : function
                this action will executed when the `BaseMenuItem` is activated (pressing Enter or by clicking on it)
        """
        z=0
        for i in items:
            # calling super.add(). Z is important to mantain order
            self.add( i, z=z )

            i.activated_effect = activated_effect
            i.selected_effect = selected_effect
            i.unselected_effect = unselected_effect
            i.item_halign = self.menu_halign
            i.item_valign = self.menu_valign
            z += 1

        self._generate_title() # If you generate the title after the items
                # the V position of the items can't consider the title's height
        if items:
            self._build_items(layout_strategy)

    def draw( self ):
        glPushMatrix()
        self.transform()
        self.title_label.draw()
        glPopMatrix()

    def on_text( self, text ):
        if text=='\r':
            return
        return self.children[self.selected_index][1].on_text(text)

    def on_key_press(self, symbol, modifiers):
        if symbol == key.ESCAPE:
            self.on_quit()
            return True
        elif symbol in (key.ENTER, key.NUM_ENTER):
            self._activate_item()
            return True
        elif symbol in (key.DOWN, key.UP):
            if symbol == key.DOWN:
                new_idx = self.selected_index + 1
            elif symbol == key.UP:
                new_idx = self.selected_index - 1

            if new_idx < 0:
                new_idx = len(self.children) -1
            elif new_idx > len(self.children) -1:
                new_idx = 0
            self._select_item( new_idx )
            return True
        else:
            # send the menu item the rest of the keys
            ret = self.children[self.selected_index][1].on_key_press(symbol, modifiers)

            # play sound if key was handled
            if ret and self.activate_sound:
                self.activate_sound.play()
            return ret

    def on_mouse_release( self, x, y, buttons, modifiers ):
        (x,y) = director.get_virtual_coordinates(x,y)
        if self.children[ self.selected_index ][1].is_inside_box(x,y):
            self._activate_item()

    def on_mouse_motion( self, x, y, dx, dy ):
        (x,y) = director.get_virtual_coordinates(x,y)
        for idx,i in enumerate( self.children):
            item = i[1]
            if item.is_inside_box( x, y):
                self._select_item( idx )
                break


class BaseMenuItem( CocosNode ):
    """An abstract menu item. It triggers a function when it is activated"""

    selected_effect = None
    unselected_effect = None
    activated_effect = None

    def __init__(self, callback_func, *args, **kwargs):
        """Creates a new menu item

        :Parameters:
            `callback_func` : function
                The callback function
        """

        super( BaseMenuItem, self).__init__()

        self.callback_func = callback_func
        self.callback_args = args
        self.callback_kwargs = kwargs

        self.is_selected = False

        self.item_halign = None
        self.item_valign = None

        self.item = None
        self.item_selected = None

    def get_item_width (self):
        """ Returns the width of the item.
            This method should be implemented by descendents.

            :rtype: int
        """
        return self.item.width

    def get_item_height (self):
        """ Returns the width of the item.
            This method should be implemented by descendents.

            :rtype: int
        """
        return self.item.height

    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
        """ Generate a normal and a selected widget.
            This method should be implemented by descendents.
        """
        raise NotImplementedError

    def get_item_x (self):
        """ Return the x position of the item.
            This method should be implemented by descendents.

            :rtype: int
        """
        return self.item.x

    def get_item_y (self):
        """ Return the y position of the item.
            This method should be implemented by descendents.

            :rtype: int
        """
        return self.item.y

    def get_box( self ):
        """Returns the box that contains the menu item.

        :rtype: (x1,x2,y1,y2)
        """
        width = self.get_item_width ()
        height = self.get_item_height ()
        if self.item_halign == CENTER:
            x_diff = - width / 2
        elif self.item_halign == RIGHT:
            x_diff = - width
        elif self.item_halign == LEFT:
            x_diff = 0
        else:
            raise Exception("Invalid halign: %s" % str(self.item_halign) )

        y_diff = - height/ 2

        x1 = self.get_item_x() + x_diff
        y1 = self.get_item_y() + y_diff
#        x1 += self.parent.x
#        y1 += self.parent.y
#        x2 = x1 + width
#        y2 = y1 + height
#        return (x1,y1,x2,y2)
        return rect.Rect(x1,y1,width,height)

    def draw( self ):
        raise NotImplementedError

    def on_key_press(self, symbol, modifiers):
        if symbol == key.ENTER and self.callback_func:
            self.callback_func(*self.callback_args, **self.callback_kwargs)
            return True

    def on_text( self, text ):
        return True

    def is_inside_box( self, x, y ):
        """Returns whether the point (x,y) is inside the menu item.

        :rtype: bool
        """
#        (ax,ay,bx,by) = self.get_box()
#        if( x >= ax and x <= bx and y >= ay and y <= by ):
#            return True
#        return False
        rect = self.get_box()
        p = self.point_to_local( (x,y) )
        return rect.contains( p.x, p.y )
        

    def on_selected( self ):
        if self.selected_effect:
            self.stop()
            self.do( self.selected_effect )

    def on_unselected( self ):
        if self.unselected_effect:
            self.stop()
            self.do( self.unselected_effect )

    def on_activated( self ):
        if self.activated_effect:
            self.stop()
            self.do( self.activated_effect )

class MenuItem (BaseMenuItem):
    """A menu item that shows a label. """
    def __init__ (self, label, callback_func, *args, **kwargs):
        """Creates a new menu item

        :Parameters:
            `label` : string
                The label the of the menu item
            `callback_func` : function
                The callback function
        """
        self.label = label
        super (MenuItem, self).__init__(callback_func, *args, **kwargs)

    def get_item_width (self):
        return self.item.content_width
    
    def get_item_height (self):
        return self.item.content_height

    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
        font_item['x'] = int(pos_x)
        font_item['y'] = int(pos_y)
        font_item['text'] = self.label
        self.item = pyglet.text.Label(**font_item )
        font_item_selected['x'] = int(pos_x)
        font_item_selected['y'] = int(pos_y)
        font_item_selected['text'] = self.label
        self.item_selected = pyglet.text.Label( **font_item_selected )

    def draw( self ):
        glPushMatrix()
        self.transform()
        if self.is_selected:
            self.item_selected.draw()
        else:
            self.item.draw()
        glPopMatrix()


class ImageMenuItem (BaseMenuItem):
    """ A menu item that shows a selectable Image """
    def __init__ (self, name, callback_func, *args, **kwargs):
        self.image = pyglet.resource.image (name)
        super (ImageMenuItem, self).__init__(callback_func, *args, **kwargs)

    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
        anchors = {'left': 0, 'center': 0.5, 'right': 1, 'top': 1, 'bottom': 0}
        anchor=(anchors[font_item['anchor_x']] * self.image.width,
                anchors[font_item['anchor_y']] * self.image.height)
        self.item = Sprite(self.image, anchor=anchor, opacity=255,
                           color=font_item['color'][:3])
        self.item.scale = font_item['font_size'] / float(self.item.height )
        self.item.position = int(pos_x), int(pos_y)
        self.selected_item = Sprite(self.image, anchor=anchor,
                                    color=font_item_selected['color'][:3])
        self.selected_item.scale = (font_item_selected['font_size'] /
                                     float(self.selected_item.height))
        self.selected_item.position = int(pos_x), int(pos_y)

    def draw (self):
        glPushMatrix()
        self.transform()
        if self.is_selected:
            self.selected_item.draw()
        else:
            self.item.draw()
        glPopMatrix()


class MultipleMenuItem( MenuItem ):
    """A menu item for switching between multiple values.

    Example::

        self.volumes = ['Mute','10','20','30','40','50','60','70','80','90','100']

        items.append( MultipleMenuItem(
                        'SFX volume: ',
                        self.on_sfx_volume,
                        self.volumes,
                        8 ) )
    """

    def __init__(self, label, callback_func, items, default_item=0):
        """Creates a Multiple Menu Item

        :Parameters:
            `label` : string
                Item's label
            `callback_func` : function
                Callback function
            `items` : list
                List of strings containing the values
            `default_item` : integer
                Default item of the list. It is an index of the list. Default: 0
        """
        self.my_label = label
        self.items = items
        self.idx = default_item
        if self.idx < 0 or self.idx >= len(self.items):
            raise Exception("Index out of bounds")
        super( MultipleMenuItem, self).__init__( self._get_label(), callback_func )

    def _get_label(self):
        return self.my_label+self.items[self.idx]

    def on_key_press(self, symbol, modifiers):
        if symbol == key.LEFT:
            self.idx = max(0, self.idx-1)
        elif symbol in (key.RIGHT, key.ENTER):
            self.idx = min(len(self.items)-1, self.idx+1)

        if symbol in (key.LEFT, key.RIGHT, key.ENTER):
            self.item.text = self._get_label()
            self.item_selected.text = self._get_label()
            self.callback_func( self.idx )
            return True

class ToggleMenuItem( MultipleMenuItem ):
    '''A menu item for a boolean toggle option.

    Example::

        items.append( ToggleMenuItem('Show FPS:', self.on_show_fps, director.show_FPS) )
    '''

    def __init__(self, label, callback_func, value=False ):
        """Creates a Toggle Menu Item

        :Parameters:
            `label` : string
                Item's label
            `callback_func` : function
                Callback function
            `value` : bool
                Default value of the item: False is 'OFF', True is 'ON'. Default:False
        """

        super(ToggleMenuItem, self).__init__( label, callback_func, ['OFF','ON'],  int(value) )

    def on_key_press( self, symbol, mod ):
        if symbol in (key.LEFT, key.RIGHT, key.ENTER):
            self.idx += 1
            if self.idx > 1:
                self.idx = 0
            self.item.text = self._get_label()
            self.item_selected.text = self._get_label()
            self.callback_func( int(self.idx) )
            return True

class EntryMenuItem(MenuItem):
    """A menu item for entering a value.

    When selected, ``self.value`` is toggled, the callback function is
    called with ``self.value`` as argument."""

    value = property(lambda self: u''.join(self._value),
                     lambda self, v: setattr(self, '_value', list(v)))

    def __init__(self, label, callback_func, value, max_length=0 ):
        """Creates an Entry Menu Item

        :Parameters:
            `label` : string
                Item's label
            `callback_func` : function
                Callback function taking one argument.
            `value` : String
                Default value: any string
            `max_length` : integer
                Maximum value length (Defaults to 0 for unbound length)
        """
        self._value = list(value)
        self._label = label
        super(EntryMenuItem, self).__init__( "%s %s" %(label,value), callback_func )
        self.max_length = max_length

    def on_text( self, text ):
        if self.max_length == 0 or len(self._value) < self.max_length:
            self._value.append(text)
            self._calculate_value()
        return True

    def on_key_press(self, symbol, modifiers):
        if symbol == key.BACKSPACE:
            try:
                self._value.pop()
            except IndexError:
                pass
            self._calculate_value()
            return True

    def _calculate_value( self ):
        new_text = u"%s %s" % (self._label, self.value)
        self.item.text = new_text
        self.item_selected.text = new_text
        self.callback_func(self.value)

class ColorMenuItem( MenuItem ):
    """A menu item for selecting a color.

    Example::

        colors = [(255, 255, 255), (100, 200, 100), (200, 50, 50)]

        items.append( ColorMenuItem(
                        'Jacket:',
                        self.on_jacket_color,
                        colors ))
    """

    def __init__(self, label, callback_func, items, default_item=0):
        """Creates a Color Menu Item

        :Parameters:
            `label` : string
                Item's label
            `callback_func` : function
                Callback function
            `items` : list
                List of thre-element tuples describing the color choices
            `default_item` : integer
                Default item of the list. It is an index of the list. Default: 0
        """
        self.my_label = label
        self.items = items
        self.idx = default_item
        if self.idx < 0 or self.idx >= len(self.items):
            raise Exception("Index out of bounds")
        super( ColorMenuItem, self).__init__( self._get_label(), callback_func )

    def _get_label(self):
        return self.my_label + "        "

    def on_key_press(self, symbol, modifiers):
        if symbol == key.LEFT:
            self.idx = max(0, self.idx-1)
        elif symbol in (key.RIGHT, key.ENTER):
            self.idx = min(len(self.items)-1, self.idx+1)

        if symbol in (key.LEFT, key.RIGHT, key.ENTER):
            self.item.text = self._get_label()
            self.item_selected.text = self._get_label()
            self.callback_func( self.idx )
            return True

    def generateWidgets (self, pos_x, pos_y, font_item, font_item_selected):
        font_item['x'] = int(pos_x)
        font_item['y'] = int(pos_y)
        font_item['text'] = self.my_label
        self.item = pyglet.text.Label(**font_item )
        self.item.labelWidth=self.item.content_width
        self.item.text = self.label
        font_item_selected['x'] = int(pos_x)
        font_item_selected['y'] = int(pos_y)
        font_item_selected['text'] = self.my_label
        self.item_selected = pyglet.text.Label( **font_item_selected )
        self.item_selected.labelWidth=self.item_selected.content_width
        self.item_selected.text = self.label

    def draw(self, *args, **kwargs):
        super(ColorMenuItem, self).draw()
        glPushMatrix()
        self.transform()

        if self.is_selected:
            item = self.item_selected
        else:
            item = self.item

        x1 = int(item._get_left() + item.labelWidth * 1.05)
        y1 = int(item.y - item.content_height / 2)
        y2 = int(item.y + item.content_height / 3)
        x2 = int(x1 + (y2 - y1) * 2)
        pyglet.graphics.draw(4, pyglet.graphics.GL_QUADS,
                             ('v2f', (x1, y1, x1, y2, x2, y2, x2, y1)),
                             ('c3B', self.items[self.idx] * 4))
        glPopMatrix()


def shake():
    '''Predefined action that performs a slight rotation and then goes back to the original rotation
    position.
    '''
    angle = 05
    duration = 0.05

    rot = Accelerate(RotateBy( angle, duration ), 2)
    rot2 = Accelerate(RotateBy( -angle*2, duration), 2)
    return rot + (rot2 + Reverse(rot2)) * 2 + Reverse(rot)

def shake_back():
    '''Predefined action that rotates to 0 degrees in 0.1 seconds'''
    return RotateTo(0,0.1)

def zoom_in():
    '''Predefined action that scales to 1.5 factor in 0.2 seconds'''
    return ScaleTo( 1.5, duration=0.2 )

def zoom_out():
    '''Predefined action that scales to 1.0 factor in 0.2 seconds'''
    return ScaleTo( 1.0, duration=0.2 )

