# A bunch of font-related effects routines used all over pydance.

import pygame
import random
from constants import *

# Text that wraps at a particular width (in pixels), automatically. No
# limit is placed on height.
class WrapFont(object):
  def __init__(self, size, width):
    self._font = pygame.font.Font(None, size)
    self._width = width
    self._ls = self._font.get_linesize()
    self._ds = - self._font.get_descent()

  def get_linesize(self): return self._ls

  # Return the number of lines a bit of text will require. indent lets you
  # set an indent (in characters) that will go before all lines after the
  # first.
  def lines(self, text, indent = ""):
    lines = 1
    words = text.split()
    start = 0
    for i in range(len(words)):
      if start == 0: line = " ".join(words[start:i+1])
      else: line = indent + " ".join(words[start:i+1])
      if self._font.size(line)[0] > self._width:
        lines += 1
        start = i
    return lines

  def size(self, text, indent = ""):
    return [self._width, self.lines(text, indent) * self._ls + self._ds]

  # Render some text, optionally with shadowing, indentation, and centering.
  # Using indentation with centered display is untested.
  def render(self, text, color = [255, 255, 255], shdw = True, indent = "",
             centered = False):
    lines = []
    words = text.split()
    start = 0
    for i in range(len(words)):
      if start == 0: line = " ".join(words[start:i+1])
      else: line = indent + " ".join(words[start:i+1])
      if self._font.size(line)[0] > self._width:
        if start == 0: line = " ".join(words[start:i])
        else: line = indent + " ".join(words[start:i])
        if shdw: t = shadow(line, self._font, color)
        else: t = self._font.render(line, True, color)
        lines.append(t)
        start = i
    if start == 0: line = " ".join(words[start:])
    else: line = indent + " ".join(words[start:])
    if shdw: t = shadow(line, self._font, color)
    else: t = self._font.render(line, True, color)
    lines.append(t)        

    image = pygame.Surface([self._width, len(lines) * self._ls + self._ds],
                           SRCALPHA, 32)
    image.fill([0, 0, 0, 0])
    for i, line in enumerate(lines):
      if centered:
        r = line.get_rect()
        r.centerx = self._width / 2
        r.y = i * self._font.get_linesize()
        image.blit(line, r)
      else:
        image.blit(line, [0, i * self._font.get_linesize()])
    return image

# EMBFADE - does a 3d emboss-like effect
def embfade(textstring, textsize, amount, displaysize, trgb = [255, 255, 255]):
  displaysurface = pygame.Surface(displaysize)
  font = pygame.font.Font(None, textsize)
  for i in range(amount):
    text = font.render(textstring, True, [c / (i + 1) for c in trgb])
    displaysurface.blit(text, [i, i])
  return displaysurface

# Do a simple drop-shadow on text, with a darker color offset by a certain
# number of pixels.
def shadow(text, font, color, offset = 1, color2 = None):
  # Allow integers or fonts for 'font'.
  if isinstance(font, int): font = pygame.font.Font(None, font)
  if color2 == None: color2 = [c / 9 for c in color]
  t1 = font.render(text, True, color)
  t2 = font.render(text, True, color2)
  s = pygame.Surface([i + offset for i in t1.get_size()], SRCALPHA, t1)
  s.blit(t2, [offset, offset])
  s.blit(t1, [0, 0])
  return s

# SHADEFADE - does a 3d dropshadow-like effect
def shadefade(textstring, textsize, amount, displaysize, trgb=(255,255,255)):
  displaysurface = pygame.Surface(displaysize, SRCALPHA, 32)
  font = pygame.font.Font(None, textsize)
  for i in range(amount - 1, 0, -1):
    text = font.render(textstring, True, [c / i for c in trgb])
    displaysurface.blit(text, [i, i])
  return displaysurface

# The rotating text on the main menu screen
class TextZoomer(object):
  def __init__(self, text, font, size, fore, back):
    self.tempsurface = pygame.surface.Surface(size)
    self.back = back
    self.fore = fore
    self.size = size
    self.font = font

    self.reset()
    self.zoomtext = text
    self.textrendered = 0

  def reset(self):
    self.mrangle = 0
    self.textrendered = 0

  def iterate(self):
    self.mrangle += 2

    zoomsurface = pygame.transform.scale(self.tempsurface,
                                         [self.size[0] + 16,
                                          self.size[1] + 16])
    zoomsurface = pygame.transform.rotate(self.tempsurface,self.mrangle)
    zoomsurface.set_alpha(120)
    zsrect = zoomsurface.get_rect()
    zsrect.center = [self.size[0] / 2, self.size[1] / 2]
    self.tempsurface.fill(self.back)
    self.tempsurface.blit(zoomsurface, zsrect)
    text = self.font.render(self.zoomtext, True, self.fore)
    trect = text.get_rect()
    self.tempsurface.blit(text, [320 - (trect.size[0]/2),
                                 32 - (trect.size[1]/2)])

# From the PCR (http://www.pygame.org/pcr/progress_text/index.php),
# by Pete Shinners.
class TextProgress(object):
  def __init__(self, font, message, color, bgcolor):
    self.font = font
    self.message = message
    self.color = color
    self.bgcolor = bgcolor
    self.offcolor = [c^40 for c in color]
    self.notcolor = [c^0xFF for c in color]
    self.text = font.render(message, True, [255, 0, 0], self.notcolor)
    self.text.set_colorkey(1, RLEACCEL)
    self.outline = self.textHollow(font, message, color)
    self.bar = pygame.Surface(self.text.get_size())
    self.bar.fill(self.offcolor)
    width, height = self.text.get_size()
    stripe = Rect(0, height/2, width, height/4)
    self.bar.fill(color, stripe)
    self.ratio = width / 100.0

  def textHollow(self, font, message, fontcolor):
    base = font.render(message, True, fontcolor, self.notcolor)
    size = base.get_width() + 2, base.get_height() + 2
    img = pygame.Surface(size, 16)
    img.fill(self.notcolor)
    base.set_colorkey(0, RLEACCEL)
    img.blit(base, [0, 0])
    img.blit(base, [2, 0])
    img.blit(base, [0, 2])
    img.blit(base, [2, 2])
    base.set_colorkey(0, RLEACCEL)
    base.set_palette_at(1, self.notcolor)
    img.blit(base, [1, 1])
    img.set_colorkey(self.notcolor, RLEACCEL)
    return img

  def render(self, percent = 50):
    surf = pygame.Surface(self.text.get_size())
    if percent < 100:
      surf.fill(self.bgcolor)
      surf.blit(self.bar, [0, 0], [0, 0, percent * self.ratio, 100])
    else:
      surf.fill(self.color)
    surf.blit(self.text, [0, 0])
    surf.blit(self.outline, [-1, -1])
    surf.set_colorkey(self.notcolor, RLEACCEL)
    return surf

# Find the appropriate font size to fit string into max_width pixels,
# that's at most max_size, and at least 6.
def max_size(string, max_width, max_size):
  for size in range(max_size, 0, -1):
    f = pygame.font.Font(None, size)
    if f.size(string)[0] < max_width: return f
  return pygame.font.Font(None, 6)

# Replace as much of the middle of the string with "..." as is necessary
# to fit it into width.
def render_outer(string, width, font):
  s = string
  if font.size(string)[0] <= width: return string
  center = len(string) / 2
  remove = 0
  while font.size(s)[0] > width:
    remove += 1
    left = center + (-remove)/2
    right = center + remove/2
    s = string[:left] + "..." + string[right:]
  return s

# Text that zooms in, then out.
# FIXME: Put some timer controls on this
class zztext(pygame.sprite.Sprite):
  def __init__(self, text, x, y):
    pygame.sprite.Sprite.__init__(self)
    self.cent = [x, y]
    self.zoom = 0
    self.baseimage = pygame.surface.Surface([320, 24])
    self.rect = self.baseimage.get_rect()
    self.rect.center = self.cent

    for i in (0, 1, 2, 3, 4, 5, 6, 7, 8, 15):
      font = pygame.font.Font(None, 9 + i)
      gtext = font.render(text, True, [i * 16] * 3)
      textpos = gtext.get_rect()
      textpos.center = [160, 12]
      self.baseimage.blit(gtext, textpos)

    self.baseimage.set_colorkey(self.baseimage.get_at([0, 0]), RLEACCEL)
    self.image = self.baseimage

  def zin(self):
    self.zoom = 1
    self.zdir = 1
      
  def zout(self):
    self.zoom = 31
    self.zdir = -1
      
  def update(self, time):
    if 32 > self.zoom > 0:
      self.image = pygame.transform.rotozoom(self.baseimage, 0, self.zoom/32.0)
      self.rect = self.image.get_rect()
      self.rect.center = self.cent
      self.zoom += self.zdir
    else:
      self.zdir = 0
