'''
AUTHOR:
  Created by Crashed on Feb 07 2009.


USAGE:
  DMIREAD(path)
	returns a list of Icons retrieved from the DMI

  DMIWRITE(icons[], path)
	given a list of Icons, this method will save the data to a dmi you specify (path)

  Icon
	How to get images from the icon:
	  Icon.icons is a map of all the images the icon holds.
	  Icon.icons[DMI_WEST][3] - fourth frame in the west direction.
	  Images without multiple frames and directions are still grabbed the same:
		  Icon.icons[DMI_SOUTH][0]

	How to get delay for each frame:
	  Delays are stored in a list, Icon.delays.
	  If there are 5 frames, there are 5 delays.
	  [1, 1, 1, 1, 1] by default.


TODO:
  Assuming reading DMIs is working flawlessly, saving DMIs would be the next task. [x]
  Reading and writing appears to work great, so what's left to do is to find bugs. [ ]
'''


import Image
import re
import os
import sys
sys.path.insert(0, os.path.split(__file__)[0])
import PngImagePlugin
import traceback
import copy


optipng = os.getcwd()


DMI_SOUTH = 0
DMI_NORTH = 1
DMI_EAST = 2
DMI_WEST = 3
DMI_SOUTHEAST = 4
DMI_SOUTHWEST = 5
DMI_NORTHEAST = 6
DMI_NORTHWEST = 7


class Icon(object):
	# Each Icon holds the information for each icon in a DMI - which includes each dir and frame

	def __init__(self):
		self.origin = None
		self.width = None
		self.height = None
		self.state = '' # icon state name
		self.dirs = 1   # number of dirs (directions)
		self.frames = 1 # number of frames
		self.icons = [] # contains all the icons for dirs and frames
		self.delays = [] # delay numbers, one for each frame
		self.loops = 0 # times to loop
		self.rewind = 0 # rewind flag
		self.movement = 0 # movement flag
		self.hotspot = [] # hotspots flag

	def __repr__(self):
		return 'Icon(origin=%r,width=%r,height=%r,state=%r,dirs=%r,frames=%r,icons=%r,delays=%r,loops=%r,rewind=%r,movement=%r,hotspot=%r)' % \
			(self.origin, self.width, self.height, self.state, self.dirs, self.frames, self.icons, self.delays, self.loops, self.rewind, self.movement, self.hotspot)

	def __str__(self):
		return 'DMI Icon (file: %s, size: %sx%s, state: "%s", dirs: %i, frames: %i, icons: %i, delays: %s, loops: %i, rewind: %i, movement: %i, hotspots: %i)' % \
			(self.origin, self.width, self.height, self.state, self.dirs, self.frames, len(flatten_array(self.icons)), self.delays, self.loops, self.rewind, self.movement, len(flatten_array(self.hotspot)))

	def copy(self, deep=True):
		icon = Icon()
		icon.state = self.state
		icon.dirs = self.dirs
		icon.frames = self.frames

		if not deep:
			# original and copied icon will share delay list and icon image objects
			# a change to one will change both
			icon.icons = self.icons
			icon.delays = self.delays
		else:
			icon.icons = [[]] * self.dirs
			for dir in xrange(self.dirs):
				icon.icons[dir] = [x.copy() for x in self.icons[dir]]
			icon.delays = copy.copy(self.delays)

		icon.loops = self.loops
		icon.rewind = self.rewind
		icon.movement = self.movement

		return icon


def DMIINFOREAD(path):
	# Return DMI metadata without cropping images

	try:
		try:
			states, (width, height) = DMIINFO(Image.open(path).info['Description'])
			return states, (width, height)

		except IOError:
			return None

		except KeyError:
			dmif = open(path, 'rb')
			if dmif.read(4).endswith('DMI'):
				print >> sys.stderr, '[DMIREAD] Failed to load icon `%s`\n\Unable to load DMI v3 icons.\n' % path
			return None

	except IOError:
		print >> sys.stderr, '[DMIREAD] File `%s` non existant.' % path
		print >> sys.stderr, traceback.format_exc()

	except Exception:
		print >> sys.stderr, '[DMIREAD]', traceback.format_exc()

	return None


def DMIINFO(info):
	# Parses DMI information.

	icon_states = [] #[(name, dirs, frames), ]
	width = -1
	height = -1

	if len(info) < 11: return -1
	if info[0:11] != '# BEGIN DMI': return -2

	groups = re.findall('(.+) = (.+)', info)
	if not groups or not len(groups): return -3

	def split_delays(delay):
		# delays are saved as 1,2,1,1 - we want to split this into a list
		if not ',' in delay and str(int(delay)) == delay:
			return int(delay)

		return [int(x) for x in delay.split(',')]

	for x in xrange(len(groups)):
		index, value = groups[x]
		if index == 'version':
			if value != '4.0':
				return -4

			if groups[x + 1][0] == '\twidth':
				x += 1
				width = int(groups[x][1])

			if groups[x + 1][0] == '\theight':
				x += 1
				height = int(groups[x][1])

		if index == 'state':
			try:
				state = unicode(value, 'utf-8')
			except:
				value

			# FIXME: make order of occurance unimportant, do a loop
			if len(groups) > x + 1 and groups[x + 1][0] == '\tdirs': # this should always exist
				x += 1
				dirs = int(groups[x][1])
			if len(groups) > x + 1 and groups[x + 1][0] == '\tframes': # this should always exist
				x += 1
				frames = int(groups[x][1])
			delays = [1]
			if len(groups) > x + 1 and groups[x + 1][0] == '\tdelay':
				x += 1
				delays = split_delays(groups[x][1])
			loops = 0
			if len(groups) > x + 1 and groups[x + 1][0] == '\tloop':
				x += 1
				loops = int(groups[x][1])
			rewind = 0
			if len(groups) > x + 1 and groups[x + 1][0] == '\trewind':
				x += 1
				rewind = int(groups[x][1])
			movement = 0
			if len(groups) > x + 1 and groups[x + 1][0] == '\tmovement':
				x += 1
				movement = int(groups[x][1])
			# seen this, but what it's used for is unknown:
			# hotspot = 1,14,1
			hotspot = []
			if len(groups) > x + 1 and groups[x + 1][0] == '\thotspot':
				x += 1
				hotspot = split_delays(groups[x][1])

			icon_states.append( (state, dirs, frames, delays, rewind, loops, movement, hotspot) )

	return icon_states, (width, height)


def DMIREAD(path):
	# Takes a path to a dmi file and splits it up into multiple Icon objects

	def DMIICON(dmi, width=-1, height=-1):
		# Splits up the DMI/PNG into multiple bitmaps.

		dmis = []

		if width < 0 or height < 0:
			width, height = dmi.size

		# actual image size
		fullwidth, fullheight = dmi.size

		for y in xrange(0, fullheight, height):
			for x in xrange(0, fullwidth, width):
				# Image.crop((left, top, right, bottom))
				dmis.append(dmi.crop((x, y, x + width, y + height)))

		return dmis

	try:
		dmifile =  os.path.split(path)[-1]
		print 'DMIREAD:', dmifile
		try:
			dmi = Image.open(path).convert('RGBA')
			#print dmi.info['Description']
			states, (width, height) = DMIINFO(dmi.info['Description'])
			#print 'icon, states: %s (width: %s; height: %s)' % (states, width, height)

			if width == -1:
				frames = 0
				for state in states:
					frames += state[1] * state[2]
				width = dmi.size[0] / frames
			if height == -1:
				height = dmi.size[1]

			dmis = DMIICON(dmi, width, height)
			if type(states) == int:
				return []

		except IOError:
			return []

		except KeyError:
			dmif = open(path, 'rb')
			if dmif.read(4).endswith('DMI'):
				print >> sys.stderr, '[DMIREAD] Failed to load icon `%s`\n\Unable to load DMI v3 icons.\n' % path
			else:
				dmi = Image.open(path).convert('RGBA')
				icon = Icon()
				icon.icons = [[dmi]]
				icon.state = ''
				icon.dirs = 1
				return [icon]
			return []

		icons = []

		dmi_counter = 0
		if states != -1:
			for index in xrange(len(states)):
				image = dmis[dmi_counter]
				state = states[index]

				icon = Icon()
				icon.origin = dmifile
				# maybe also the path from project's root?
				icon.icons = [[image]]
				icon.width = width
				icon.height = height
				icon.state = state[0][1:-1]
				icon.dirs = state[1]
				icon.frames = state[2]
				icon.delays = state[3]
				icon.rewind = state[4]
				icon.loops = state[5]
				icon.movement = state[6]
				icon.hotspot = state[7]

				print 'DEBUG: %s' % icon

				if icon.dirs > 1:
					for x in xrange(1, icon.dirs):
						dmi_counter += 1
						image = dmis[dmi_counter]
						icon.icons.append([image])

				if icon.frames > 1: