#!/usr/bin/env python
"""
Footprint PCB layouting program
Copyright (C) 2012 Danny Milosavljevic
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

from __future__ import generators
from primitives import From, MList
import metafile
from ll import Object
	
class Selection(object): # viewmodel
	def __init__(self, items, connections):
		self.items = set(items)
		self.connections = set(connections) # TODO monitor...
		self.rect = MList([0, 0, 0, 0]) # the latest one, that is.
	def startSelectionRect(self, x, y):
		self.rect[0] = x
		self.rect[1] = y
	def stopSelectionRect(self, x, y):
		self.rect[2] = x
		self.rect[3] = y
	def addItem(self, item):
		self.items.add(item)
	def removeItem(self, item):
		self.items.remove(item)
	def clearItems(self):
		self.items = set()

def intersect(r0, r1):
	x0 = max(r0[0], r1[0])
	x1 = min(r0[2], r1[2])
	y0 = max(r0[1], r1[1])
	y1 = min(r0[3], r1[3])
	return x0, y0, x1, y1
def positiveAreaP(r):
	x0, y0, x1, y1 = r
	return x0 < x1 and y0 < y1

def transform(transM, scale, *coo):
	if len(coo) == 2:
		coo = coo[0]*scale[0], coo[1]*scale[1]
		a = transM[0][0]*coo[0] + transM[0][1]*coo[1] + transM[0][2]
		b = transM[1][0]*coo[0] + transM[1][1]*coo[1] + transM[1][2]
		return a, b
	else:
		coo = coo[0]*scale[0], coo[1]*scale[1], coo[2]*scale[0], coo[3]*scale[1]
		a = transM[0][0]*coo[0] + transM[0][1]*coo[1] + transM[0][2]
		b = transM[1][0]*coo[0] + transM[1][1]*coo[1] + transM[1][2]
		c = transM[0][0]*coo[2] + transM[0][1]*coo[3] + transM[0][2]
		d = transM[1][0]*coo[2] + transM[1][1]*coo[3] + transM[1][2]
		return a, b, c, d
# we assume transM^T transM = 1
def untransform(transM, scale, *coo):
	if len(coo) == 2:
		coo = coo[0]/scale[0], coo[1]/scale[1]
		a = transM[0][0]*coo[0] + transM[1][0]*coo[1] + transM[2][0]
		b = transM[0][1]*coo[0] + transM[1][1]*coo[1] + transM[2][1]
		return a, b
	else:
		coo = coo[0]/scale[0], coo[1]/scale[1], coo[2]/scale[0], coo[3]/scale[1]
		a = transM[0][0]*coo[0] + transM[1][0]*coo[1] + transM[2][0]
		b = transM[0][1]*coo[0] + transM[1][1]*coo[1] + transM[2][1]
		c = transM[0][0]*coo[2] + transM[1][0]*coo[3] + transM[2][0]
		d = transM[0][1]*coo[2] + transM[1][1]*coo[3] + transM[2][1]
		return a, b, c, d

layerID = 0
class Layer(Object):
	def __init__(self, model, name = None, keyboardShortcut = None):
		Object.__init__(self)
		global layerID
		self.model = model
		self.keyboardShortcut = keyboardShortcut
		self.name = name or "Layer%d" % (layerID)
		layerID += 1
		self.scale = (1,1) # just a copy!
		self.transM = [[1,0,0],
		               [0,1,0],
		               [0,0,1]] # just a copy!
	def transform(self, *coo):
		result = transform(self.transM, self.scale, *coo)
		return(result)
	def drawItem(self, item):
		rect = self.transform(item.rect)
		yield From(item.repaint(rect))
		#self.drawBBox(rect)
	def drawBBox(self, trect, lineStyle): # rect is already transformed
		yield metafile.makeOperation("lineStyle", lineStyle)
		yield metafile.makeOperation("rectangle", trect[0], trect[1], trect[2], trect[3], 2, False)
	def drawConnection(self, connection):
		return []
	def drawItemBorder(self, item, bSelected):
		rect = self.transform(item.rect)
		yield From(self.drawBBox(rect, "solid" if not bSelected else "dashed1"))
	def repaintGrid(self, area):
		M = 1000 # FIXME
		gridPitch = 1 # mm
		gridPitch, gridPitch = self.transform(gridPitch, gridPitch)
		x0, y0, x1, y1 = self.transform(0, 0, M, M)
		yield metafile.makeOperation("grid", x0, y0, x1, y1, gridPitch, gridPitch)
	def repaint(self, area): # return metafile...
		model = self.model
		yield From(self.repaintGrid(area))
		for item in model.items:
			if item.layer is self:
				# TODO check bbox for partial views.
				yield From(self.drawItem(item))
		for connection in model.connections:
			if item.layer is self:
				yield From(self.drawConnection(connection))
	def repaintSelectors(self, area, selection):
		model = self.model
		for item in model.items:
			if item.layer is self:
				yield From(self.drawItemBorder(item, item in selection.items))
	def handleEvent(self, operation, *operands):
		return False
	def findInRect(self, rect):
		for item in self.items:
			xrect = self.transform(item.rect)
			irect = intersect(rect, xrect)
			if positiveAreaP(irect):
				yield item
	@classmethod
	def getCreationParameters(klass):
                yield ("name", None, "Name")

class View(object):
	def __init__(self, layers, model = None):
		self.model = model # default model, not that important...
		self.layers = MList(layers) # [(True, layer) for layer in layers]
		self._layer = None
		self.layerMonitors = MList()
		self.layerMonitors.monitors.cons(self.handleColdplug)
		self.redrawMonitors = MList()
		self.tool = None # set by Workbench.
		self._scale = (3,3)
		self._transM = [[1,0,0],
		                [0,1,0],
		                [0,0,1]]
		self.selection = Selection([], [])
		self.selection.rect.monitors.cons(self.updateSelection)
		if len(layers) > 0 and self._layer is None: # for convenience, select the first layer (any layer, really)
			for bActive, layer in layers:
				if bActive:
					self.layer = layer
					break
		self.handleColdplug()
	def updateSelection(self, *args, **kwargs):
		self.queue_draw()
	def startSelectionRect(self, *coo):
		self.selection.startSelectionRect(*coo)
	def stopSelectionRect(self, *coo):
		self.selection.stopSelectionRect(*coo)
	def eatSelectionRect(self, mode):
		rect = self.selection.rect
		selection = self.selection
		if mode == "replace":
			selection.clearItems()
		for item in self.findInRect(rect):
			if mode == "add" or mode == "replace":
				selection.addItem(item)
			elif mode == "remove":
				selection.removeItem(item)
	def untransform(self, *coo):
		return untransform(self.transM, self.scale, *coo)
	def transform(self, *coo):
		return transform(self.transM, self.scale, *coo)
	@property
	def scale(self):
		return(self._scale)
	@scale.setter
	def scale(self, value):
		self._scale = value
		for bActive, layer in self.layers:
			layer.scale = value
		# TODO notify?
	@property
	def transM(self):
		return(self._transM)
	@transM.setter
	def transM(self, value):
		self._transM = value
		for bActive, layer in self.layers:
			layer._transM = value
	def handleColdplug(self, *args):
		value = self._transM
		for bActive, layer in self.layers:
			layer.transM = value
			layer.scale = self.scale
	@property
	def layer(self):
		return(self._layer)
	@layer.setter
	def layer(self, value):
		if self._layer is not value:
			self._layer = value
			self.notifyLayerMonitors("change", "layer", value)
	def notifyLayerMonitors(self, action, name,  value):
		return any([callback(action, name, value) for callback in self.layerMonitors])
	def repaint(self, area): # return metafile
		for bVisible, layer in self.layers:
			if bVisible:
				yield From(layer.repaint(area))
		selection = self.selection
		for bVisible, layer in self.layers:
			if bVisible:
				yield From(layer.repaintSelectors(area, selection))
		trect = self.transform(*self.selection.rect)
		#print("R", trect)
		yield metafile.makeOperation("lineStyle", "dashed1")
		yield metafile.makeOperation("rectangle", trect[0], trect[1], trect[2], trect[3], 2, False)
	def handleEvent(self, operation, *operands):
		for bVisible, layer in self.layers:
			if bVisible:
				if layer.handleEvent(operation, *operands):
					return True
		return False
	def findInRect(self, rect):
		for bVisible, layer in self.layers:
			if bVisible:
				yield From(layer.findInRect(rect))
	def queue_draw(self):
		return any([callback() for callback in self.redrawMonitors])
	def createLayer(self):
		layer = Layer(self.model)
		return(layer)
