#!/usr/bin/env python

# Copyright (C) 2010-2012  Sam Bull

"""
Base widget, all widgets inherit from this.

"""

import pygame
from pygame.locals import Rect

from _locals import *
from _locals import (has_focus, is_active, add_widget, remove_widget_order,
                     set_cursor, remove_cursor)
try:
    import opengl
except ImportError:
    print "OpenGL widgets disabled."

class Simple(pygame.sprite.Sprite):

    """
    Widget foundations all widgets should inherit from.
    This can also be used as a simple widget that does nothing, such as
    displaying an image.

    Attributes:
      image: The current surface that will be drawn to the screen.
      rect: The ``pygame.Rect`` used for the widget's position and size.
      rect_abs: A ``pygame.Rect`` using the absolute screen position.
      pos: The widget's position. Can be retrieved or assigned as a shortcut
          for rect.topleft. Also a shortcut for setting pos through config().
      pos_abs: The widget's absolute screen position.

    """

    # Widget settings
    _can_focus = False
    _modal = False
    _layered = False  # Layered updates for dialog windows etc.
    _default_size = None
    _parent = None
    _available_images = ()
    _extra_images = {}
    _settings_default = {}

    _fade = None  # Alpha level when fading
    _fade_up = True
    _custom_image = False

    def __init__(self, surf=None, **kwargs):
        """
        Args:
          surf: The surface that should be drawn to screen, of type:
            pygame.Surface: Use an existing surface.
            tuple,list: Contains size as (width,height), creates a new surface.
            str: Contains file name to load an image.
            dict: Contains multiple images to be loaded. The documentation will
                specify if a widget uses multiple images and what names to use.
          kwargs: Any number of keyword arguments matching those for config().

        """
        pygame.sprite.Sprite.__init__(self)

        # Implicitly pass attributes into _draw()
        # Used to reduce complexity for widget developers
        draw = self._draw
        self._draw = lambda d=self.get_draw(): draw(d)

        # Initialise attributes
        self._images = {}
        self._available_images = ("image",) + self._available_images
        self._settings = self._settings_default.copy()
        self.rect = Rect((0,0), (0,0))

        # Use default size if none specified
        if surf is None:
            surf = self._default_size

        # Create base surfaces if not None.
        # If None, widget is expected to call this function later.
        if surf is not None:
            self._create_base_images(surf)

        self.config(init=None, **kwargs)


    def config(self, **kwargs):
        """
        Update widget configuration and redraw the widget.

        Keyword Args:
          pos: ``tuple`` (x,y) Position to set widget to.

        """
        if "pos" in kwargs:
            self.rect.topleft = kwargs["pos"]
        self._config(**kwargs)
        self._draw()

    def _config(self, **kwargs):
        """Widgets should overload for custom widget configuration."""
        pass

    def add(self, order=None, fade=True):
        """
        Add widget to screen.

        Args:
          order: Integer representing the order widget should receive focus
              when user presses TAB. The widget with the lowest order will
              receive focus first, then moving up with increasing values.
          fade: True if widget should fade in, False if not.

        """
        added = add_widget(self, order)

        # Fade widget in
        if fade:
            self._fade_up = True
            if added and self._fade is None: self._fade = 1
            self.image.set_alpha(self._fade)
        else:
            self._fade = None
            self.image.set_alpha(255)

    def remove(self, fade=True):
        """
        Remove widget from screen.

        Args:
          fade: True if widget should fade out.

        """
        if fade:  # Fade widget out
            self._fade_up = False
            if self._fade == None: self._fade = 250
        else:  # Remove widget immediately
            self.kill()
        remove_widget_order(self)

    def active(self):
        """Return True if widget is active (onscreen)."""
        return is_active(self)

    def has_focus(self):
        """Return True if this widget has focus."""
        return has_focus(self)

    def get_draw(self):
        """
        Return appropriate draw module for pygame or OpenGL.

        Use like:
          ``draw = self.get_draw()``
          ``draw.rect(self.image, ...)``

        """
        if not get_screen()._opengl:
            return pygame.draw
        else:
            return opengl.draw

    def update(self, time):
        """Placeholder for function that updates the widget per frame."""
        pass

    def _event(self, event):
        """Placeholder for function that receives event for the widget."""
        pass

    def _draw(self, draw):
        """Widgets should overload to draw default images."""
        pass

    def _create_base_images(self, surf, parent=None):
        """
        Creates the base surfaces to draw on, or uses existing images.

        If self._default_size is None, widget is expected to call this
        function manually when no size is given.

        """
        Image = opengl.OpenGLImage if get_screen()._opengl else pygame.Surface
        def create_image(surf):
            """Return a created surface."""
            if isinstance(surf, pygame.Surface):
                surf.set_colorkey(0)
                self._custom_image = True
                return surf
            elif isinstance(surf, (tuple,list)):
                self._custom_image = False
                if isinstance(surf[0], (tuple,list)):
                    surf = (self.rect.w * surf[0][0] + surf[0][1],
                            self.rect.h * surf[1][0] + surf[1][1])
                surf = Image(surf)
                surf.set_colorkey(0)
                return surf
            elif isinstance(surf, str):
                self._custom_image = True
                return pygame.image.load(surf).convert_alpha()

        # Create base images
        if isinstance(surf, dict):
            for img in surf:
                assert (img in self._available_images or
                        img in self._extra_images), "Incorrect image."
                self._images[img] = create_image(surf[img])
        else:
            self._images["image"] = create_image(surf)

        # Copy other images, if any have not been supplied.
        assert "image" in self._images, "Must supply 'image'"
        for count, name in enumerate(self._available_images):
            if name not in self._images:
                img = self._images[self._available_images[count-1]]
                self._images[name] = img.copy()

        self.image = self._images["image"].copy()
        self.rect.size = self.image.get_size()

        # Set up extra images
        for name in self._extra_images:
            if name not in self._images:
                self._images[name] = create_image(self._extra_images[name])

    def _change_focus(self, forward=True):
        """
        Called when focus should be changed. Container widget
        should override this function.

        Args:
          forward: True if toggling focus forwards, False if backwards.

        Returns:
          True if widget should change focus from this widget.

        """
        return True

    def _focus_enter(self, focus=0):
        """
        Called when the widget gains focus.

        Args:
          focus: 1 if focused by keyboard, 2 if by mouse.

        """
        pass

    def _focus_exit(self):
        """Called when the widget loses focus."""
        pass

    def _dotted_rect(self, col=(255,255,255)):
        """Draw a dotted rectangle to show keyboard focus."""
        self.image.lock()
        for i in range(0, self.rect.w, 3):
            # Draw horizontal lines
            self.image.set_at((i, 0), col)
            self.image.set_at((i, self.rect.h-1), col)
        for i in range(0, self.rect.h, 2):
            # Draw vertical lines
            self.image.set_at((0, i), col)
            self.image.set_at((self.rect.w-1, i), col)
        self.image.unlock()

    def _set_cursor(self, size, hotspot, xormasks, andmasks):
        set_cursor(self, size, hotspot, xormasks, andmasks)

    def _remove_cursor(self):
        remove_cursor(self)


    # --PROPERTIES--

    @property
    def rect_abs(self):
        if self._parent is None:
            return self.rect
        else:
            p_abs = self._parent.pos_abs
            p = (self.rect.x + p_abs[0], self.rect.y + p_abs[1])
            return Rect(p, self.rect.size)

    @property
    def pos(self):
        return self.rect.topleft
    @pos.setter
    def pos(self, value):
        self.rect.topleft = value
    @property
    def pos_abs(self):
        if self._parent is None:
            return self.rect.topleft
        else:
            p_abs = self._parent.pos_abs
            return (self.rect.x + p_abs[0], self.rect.y + p_abs[1])