# -*- coding: utf-8 -*-
#
#	Copyright (C) 2011-2013 by Igor E. Novikov
#
#	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 copy import deepcopy


from uc2 import uc2const
from uc2 import _, cms
from uc2 import libgeom
from uc2.formats.pdxf import const
from uc2.formats.generic import ModelObject


# Document object enumeration
DOCUMENT = 1

METAINFO = 10
STYLES = 11
STYLE = 12
PROFILES = 13
PROFILE = 14
FONTS = 15
FONT = 16
IMAGES = 17
IMAGE = 18

STRUCTURAL_CLASS = 50
PAGES = 51
PAGE = 52
LAYER_GROUP = 53
MASTER_LAYERS = 54
LAYER = 55
GRID_LAYER = 57
GUIDE_LAYER = 58
DESKTOP_LAYERS = 59
GUIDE = 60

SELECTABLE_CLASS = 100
COMPOUND_CLASS = 101
GROUP = 102
CONTAINER = 103

BITMAP_CLASS = 150
PIXMAP = 151

PRIMITIVE_CLASS = 200
RECTANGLE = 201
CIRCLE = 202
POLYGON = 203
CURVE = 204
TEXT_BLOCK = 205
TEXT_COLUMN = 206

CID_TO_NAME = {
	DOCUMENT: _('Document'),

	METAINFO: _('Metainfo'), STYLES: _('Styles'), STYLE: _('Style'),
	PROFILES: _('Profiles'), PROFILE: _('Profile'), FONTS: _('Fonts'),
	FONT: _('Font'), IMAGES: _('Images'), IMAGE: _('Image'),

	PAGES: _('Pages'), PAGE: _('Page'), LAYER_GROUP: _('Layer group'),
	MASTER_LAYERS: _('Master layers'), LAYER: _('Layer'),
	GRID_LAYER: _('Grid layer'), GUIDE_LAYER: _('Guide layer'),
	DESKTOP_LAYERS: _('Desktop layers'), GUIDE: _('Guide'),

	GROUP: _('Group'), CONTAINER: _('Container'),
	TEXT_BLOCK: _('Text block'), TEXT_COLUMN: _('Text column'),

	RECTANGLE: _('Rectangle'), CIRCLE: _('Ellipse'),
	POLYGON: _('Polygon'), CURVE: _('Curve'),
	PIXMAP: _('Pixmap'),
	}


CID_TO_TAGNAME = {
	DOCUMENT: 'Document',

	METAINFO: 'Metainfo', STYLES: 'Styles', STYLE: 'Style',
	PROFILES: 'Profiles', PROFILE: 'Profile', FONTS: 'Fonts',
	FONT: 'Font', IMAGES: 'Images', IMAGE: 'Image',

	PAGES: 'Pages', PAGE: 'Page', LAYER_GROUP: 'LayerGroup',
	MASTER_LAYERS: 'MasterLayers', LAYER: 'Layer',
	GRID_LAYER: 'GridLayer', GUIDE_LAYER: 'GuideLayer',
	DESKTOP_LAYERS: 'DesktopLayers', GUIDE: 'Guide',

	GROUP: 'Group', CONTAINER: 'Container',
	TEXT_BLOCK: 'TextBlock', TEXT_COLUMN: 'TextColumn',

	RECTANGLE: 'Rectangle', CIRCLE: 'Ellipse',
	POLYGON: 'Polygon', CURVE: 'Curve',
	PIXMAP: 'Pixmap',
	}

TAGNAME_TO_CID = {
	'Document': DOCUMENT,

	'Metainfo': METAINFO, 'Styles': STYLES, 'Style': STYLE,
	'Profiles': PROFILES, 'Profile': PROFILE, 'Fonts': FONTS,
	'Font': FONT, 'Images': IMAGES, 'Image': IMAGE,

	'Pages': PAGES, 'Page': PAGE, 'LayerGroup': LAYER_GROUP,
	'MasterLayers': MASTER_LAYERS, 'Layer': LAYER,
	'GridLayer': GRID_LAYER, 'GuideLayer': GUIDE_LAYER,
	'DesktopLayers': DESKTOP_LAYERS, 'Guide': GUIDE,

	'Group': GROUP, 'Container': CONTAINER,
	'TextBlock': TEXT_BLOCK, 'TextColumn': TEXT_COLUMN,

	'Rectangle': RECTANGLE, 'Ellipse': CIRCLE,
	'Polygon': POLYGON, 'Curve': CURVE,
	'Pixmap': PIXMAP,
	}

class DocumentObject(ModelObject):
	"""
	Abstract parent class for all document 
	objects. Provides common object properties.
	"""

	def resolve(self):
		is_leaf = True
		if self.cid < PRIMITIVE_CLASS: is_leaf = False
		if self.cid == GUIDE: is_leaf = True
		name = CID_TO_NAME[self.cid]
		info = ''
		return (is_leaf, name, info)

	def get_resources(self):
		return []

	def copy(self, src=None, dst=None):
		return None


class Document(DocumentObject):
	"""
	Represents PrintDesign Document object.
	This is a root DOM instance of PDXF file format.
	"""
	cid = DOCUMENT
	metainfo = None
	styles = {}
	profiles = []
	doc_origin = 1
	doc_units = uc2const.UNIT_MM
	resources = {}

	def __init__(self, config):
		self.cid = DOCUMENT
		self.childs = []
		self.metainfo = None
		self.config = config
		self.doc_origin = self.config.doc_origin
		self.doc_units = self.config.doc_units
		self.styles = {}
		self.styles["Default Style"] = [deepcopy(self.config.default_fill),
									deepcopy(self.config.default_stroke),
									deepcopy(self.config.default_text_style),
									deepcopy(self.config.default_structural_style)]
		self.resources = {}



class Pages(DocumentObject):
	"""
	Represents container for pages. 
	Stores default page format value and page objects in childs list. 
	Also has a page counter field.
	Page format: [format name, (width, height), orientation]
	"""
	cid = PAGES
	page_format = []
	page_counter = 0

	def __init__(self, config, parent=None):
		self.cid = PAGES
		self.childs = []
		self.page_counter = 0
		self.parent = parent
		self.config = config
		fmt = '' + self.config.page_format
		size = deepcopy(uc2const.PAGE_FORMATS[fmt])
		orient = config.page_orientation
		self.page_format = [fmt, size, orient]



#================Structural Objects==================

class StructuralObject(DocumentObject):
	"""
	Abstract parent for structural objects.
	"""
	cid = STRUCTURAL_CLASS
	name = ''
	style = [[], [], [], []]

class Page(StructuralObject):
	"""
	Represents document page.
	The object stores page name, page format and layer counter.
	All child layers are in childs list.
	Page format: [format name, (width, height), orientation]
	"""
	cid = PAGE
	page_format = []
	name = ''

	layer_counter = 0

	def __init__(self, config, parent=None , name=''):
		self.cid = PAGE
		self.childs = []
		self.layer_counter = 0
		self.parent = parent
		self.config = config
		if not name:
			self.name = _('Page') + ' 1'
		else:
			self.name = name
		if parent is None:
			fmt = '' + self.config.page_format
			size = deepcopy(uc2const.PAGE_FORMATS[fmt])
			orient = config.page_orientation
			self.page_format = [fmt, size, orient]
		else:
			self.page_format = deepcopy(parent.page_format)

	def resolve(self):
		is_leaf = False
		name = '%s' % (self.name)
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)


class Layer(StructuralObject):
	"""
	Represents document layer.
	The object stores layer name and color used for contour view mode.
	All child objects are in childs list.
	"""
	cid = LAYER
	color = ''
	properties = []
	name = ''

	def __init__(self, config, parent=None, name=''):
		self.cid = LAYER
		self.childs = []
		self.config = config

		if not name:
			self.name = _('Layer') + ' 1'
		else:
			self.name = name

		self.parent = parent
		self.color = '' + self.config.layer_color
		self.style = [[], deepcopy(self.config.default_stroke), [], []]
		self.properties = [] + self.config.layer_propeties
		self.childs = []

	def resolve(self):
		is_leaf = False
		name = '%s' % (self.name)
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)

	def update(self):
		if isinstance(self.color, str):
			try:
				self.color = cms.hexcolor_to_rgba(self.color)
			except:
				self.color = cms.hexcolor_to_rgba(self.config.layer_color)
		stroke = self.style[1]
		if stroke:
			stroke[2] = [uc2const.COLOR_RGB , self.color, 1.0, '']

class GuideLayer(Layer):
	"""
	Represents guide line layer.
	This is a special layer to store document guide lines. 
	The object stores layer name and color used for guide line rendering.
	All child objects are in childs list.
	"""
	cid = GUIDE_LAYER

	def __init__(self, config, parent=None, name=_('GuideLayer')):
		Layer.__init__(self, config, parent, name)
		self.cid = GUIDE_LAYER
		self.childs = []
		self.color = '' + self.config.guide_layer_color
		self.properties = [] + self.config.guide_layer_propeties

	def resolve(self):
		is_leaf = False
		name = '%s' % (CID_TO_NAME[self.cid])
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)

class GridLayer(Layer):
	"""
	Represents guide line layer.
	This is a special layer to manage document grid. 
	The object stores layer name and color used for grid rendering.
	All child objects are in childs list.
	"""
	cid = GRID_LAYER
	grid = []

	def __init__(self, config, parent=None, name=_('GridLayer')):
		Layer.__init__(self, config, parent, name)
		self.cid = GRID_LAYER
		self.childs = []
		self.color = [] + self.config.grid_layer_color
		self.grid = [] + self.config.grid_layer_geometry
		self.properties = [] + self.config.grid_layer_propeties

	def resolve(self):
		is_leaf = False
		name = '%s' % (CID_TO_NAME[self.cid])
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)

class LayerGroup(StructuralObject):
	"""
	Represents container for regular layers.
	The object is not used yet. 
	All child layers are in childs list.
	"""
	cid = LAYER_GROUP
	layer_counter = 0

	def __init__(self, config, parent=None):
		self.cid = LAYER_GROUP
		self.childs = []
		self.parent = parent
		self.config = config
		self.childs = []

class MasterLayers(LayerGroup):
	"""
	Represents container for master layers applied for all pages.
	This layer group is the top most.
	All child layers are in childs list.
	"""
	cid = MASTER_LAYERS

	def __init__(self, config, parent=None):
		LayerGroup.__init__(self, config, parent)
		self.cid = MASTER_LAYERS
		self.childs = []

	def resolve(self):
		is_leaf = False
		name = '%s' % (CID_TO_NAME[self.cid])
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)

class DesktopLayers(LayerGroup):
	"""
	Represents container for desktop layers applied for all pages.
	This layer group is the lowest.
	All child layers are in childs list.
	"""
	cid = DESKTOP_LAYERS

	def __init__(self, config, parent=None):
		LayerGroup.__init__(self, config, parent)
		self.cid = DESKTOP_LAYERS
		self.childs = []

	def resolve(self):
		is_leaf = False
		name = '%s' % (CID_TO_NAME[self.cid])
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)

class Guide(StructuralObject):
	"""
	Represents container for regular layers.
	The object is not used yet. 
	All child layers are in childs list.
	"""
	cid = GUIDE
	orientation = uc2const.HORIZONTAL
	position = 0.0
	def __init__(self, config, parent=None, pos=0.0, orient=uc2const.HORIZONTAL):
		self.config = config
		self.parent = parent
		self.cid = GUIDE
		self.position = pos
		self.orientation = orient
		self.childs = []

#================Selectable Objects==================
class SelectableObject(DocumentObject):
	"""
	Abstract parent class for selectable objects. 
	Provides common selectable object properties.
	"""
	cid = SELECTABLE_CLASS
	trafo = []
	style = [[], [], [], []]

	cache_bbox = []

	def to_curve(self): return None


#---------------Compound objects---------------------
class Group(SelectableObject):

	cid = GROUP
	childs = []

	def __init__(self, config, parent=None, childs=[]):
		self.cid = GROUP
		self.childs = []
		self.config = config
		self.parent = parent
		self.childs += childs

	def copy(self):
		childs_copy = []
		for child in self.childs:
			childs_copy.append(child.copy())
		return Group(self.config, None, childs_copy)

	def apply_trafo(self, trafo):
		for child in self.childs:
			child.apply_trafo(trafo)
		self.update_bbox()

	def update_bbox(self):
		if self.childs:
			self.cache_bbox = deepcopy(self.childs[0].cache_bbox)
			for child in self.childs[1:]:
				self.cache_bbox = libgeom.sum_bbox(self.cache_bbox,
												child.cache_bbox)

	def update(self):
		for child in self.childs:
			child.update()
		self.update_bbox()

	def get_trafo_snapshot(self):
		childs_snapshots = []
		for child in self.childs:
			childs_snapshots.append(child.get_trafo_snapshot())
		return (self, None, [] + self.cache_bbox, childs_snapshots)

	def set_trafo_snapshot(self, snapshot):
		self.cache_bbox, childs_snapshots = snapshot[2:]
		for item in childs_snapshots:
			item[0].set_trafo_snapshot(item)

	def resolve(self):
		is_leaf = False
		name = '%s' % (CID_TO_NAME[self.cid])
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)

class Container(Group):

	cid = CONTAINER
	cache_container = None

	def __init__(self, config, parent=None, childs=[]):
		self.cid = CONTAINER
		self.childs = []
		self.config = config
		self.parent = parent
		self.childs += childs

	def update(self):
		for child in self.childs:
			child.update()
		self.update_bbox()

	def copy(self):
		childs_copy = []
		for child in self.childs:
			childs_copy.append(child.copy())
		return Container(self.config, None, childs_copy)

	def update_bbox(self):
		self.cache_container = self.childs[0]
		self.cache_bbox = deepcopy(self.cache_container.cache_bbox)

	def resolve(self):
		is_leaf = False
		name = '%s' % (CID_TO_NAME[self.cid])
		info = '%d' % (len(self.childs))
		return (is_leaf, name, info)

#---------------Bitmap objects-----------------------

class Pixmap(SelectableObject):pass

#---------------Primitives---------------------------
class PrimitiveObject(SelectableObject):

	cid = PRIMITIVE_CLASS

	cache_paths = None
	cache_cpath = None

	def copy(self):pass
	def get_initial_paths(self): pass

	def destroy(self):
		if not self.cache_cpath is None:
			del self.cache_cpath
		SelectableObject.destroy(self)

	def to_curve(self):
		curve = Curve(self.config)
		curve.paths = deepcopy(self.cache_paths)
		curve.trafo = [] + self.trafo
		curve.style = deepcopy(self.style)
		curve.update()
		return curve

	def update(self):
		self.cache_paths = self.get_initial_paths()
		self.cache_cpath = libgeom.create_cpath(self.cache_paths)
		libgeom.apply_trafo(self.cache_cpath, self.trafo)
		self.update_bbox()

	def update_bbox(self):
		self.cache_bbox = libgeom.get_cpath_bbox(self.cache_cpath)

	def apply_trafo(self, trafo):
		self.cache_cpath = libgeom.apply_trafo(self.cache_cpath, trafo)
		self.trafo = libgeom.multiply_trafo(self.trafo, trafo)
		self.update_bbox()

	def get_trafo_snapshot(self):
		return (self, [] + self.trafo, [] + self.cache_bbox,
			libgeom.copy_cpath(self.cache_cpath))

	def set_trafo_snapshot(self, snapshot):
		self.trafo, self.cache_bbox, self.cache_cpath = snapshot[1:]


class Rectangle(PrimitiveObject):

	cid = RECTANGLE
	start = []
	width = 1.0
	height = 1.0
	corners = []

	def __init__(self, config, parent=None,
				rect=[] + const.STUB_RECT,
				trafo=[] + const.NORMAL_TRAFO,
				style=[] + const.EMPTY_STYLE,
				corners=[] + const.CORNERS,
				):
		self.cid = RECTANGLE
		self.parent = parent
		self.config = config
		self.start = [rect[0], rect[1]]
		self.width = rect[2]
		self.height = rect[3]
		self.trafo = trafo
		self.corners = corners
		self.style = style

	def copy(self):
		rect = Rectangle(self.config)
		rect.start = [] + self.start
		rect.width = self.width
		rect.height = self.height
		rect.trafo = [] + self.trafo
		rect.corners = [] + self.corners
		rect.style = deepcopy(self.style)
		return rect

	def get_initial_paths(self):
		return libgeom.get_rect_paths(self.start, self.width,
									self.height, self.corners)


class Circle(PrimitiveObject):

	cid = CIRCLE
	angle1 = 0.0
	angle2 = 0.0
	circle_type = const.ARC_CHORD
	initial_trafo = const.NORMAL_TRAFO

	def __init__(self, config, parent=None,
				rect=[] + const.STUB_RECT,
				angle1=0.0,
				angle2=0.0,
				circle_type=const.ARC_CHORD,
				style=[] + const.EMPTY_STYLE,
				):
		self.cid = CIRCLE
		self.parent = parent
		self.config = config
		self.angle1 = angle1
		self.angle2 = angle2
		self.trafo = [rect[2], 0.0, 0.0, rect[3], rect[0], rect[1]]
		self.initial_trafo = [] + self.trafo
		self.circle_type = circle_type
		self.style = style

	def copy(self):
		circle = Circle(self.config)
		circle.trafo = [] + self.trafo
		circle.initial_trafo = [] + self.initial_trafo
		circle.angle1 = self.angle1
		circle.angle2 = self.angle2
		circle.circle_type = self.circle_type
		circle.style = deepcopy(self.style)
		return circle

	def get_initial_paths(self):
		return libgeom.get_circle_paths(self.angle1, self.angle2, self.circle_type)


class Polygon(PrimitiveObject):

	cid = POLYGON

	corners_num = 0
	angle1 = 0.0
	angle2 = 0.0
	coef1 = 1.0
	coef2 = 1.0
	initial_trafo = const.NORMAL_TRAFO

	def __init__(self, config, parent=None,
				rect=[] + const.STUB_RECT,
				angel1=0.0,
				angel2=0.0,
				coef1=1.0,
				coef2=1.0,
				corners_num=0,
				style=[] + const.EMPTY_STYLE,
				):
		self.cid = POLYGON
		self.parent = parent
		self.config = config
		self.corners_num = corners_num
		if not corners_num:
			self.corners_num = config.default_polygon_num
		self.angle1 = angel1
		self.angle2 = angel2
		self.coef1 = coef1
		self.coef2 = coef2
		self.trafo = [rect[2], 0.0, 0.0, rect[3], rect[0], rect[1]]
		self.initial_trafo = [] + self.trafo
		self.style = style

	def copy(self):
		polygon = Polygon(self.config)
		polygon.trafo = [] + self.trafo
		polygon.initial_trafo = [] + self.initial_trafo
		polygon.corners_num = self.corners_num
		polygon.angle1 = self.angle1
		polygon.angle2 = self.angle2
		polygon.coef1 = self.coef1
		polygon.coef2 = self.coef2
		polygon.style = deepcopy(self.style)
		return polygon

	def get_initial_paths(self):
		return libgeom.get_polygon_paths(self.corners_num,
									self.angle1, self.angle2,
									self.coef1, self.coef2)

class Curve(PrimitiveObject):

	cid = CURVE
	paths = []

	def __init__(self, config, parent=None,
				paths=[] + const.STUB_PATHS,
				trafo=[] + const.NORMAL_TRAFO,
				style=[] + const.EMPTY_STYLE):
		self.cid = CURVE
		self.config = config
		self.parent = parent
		self.paths = paths
		self.trafo = trafo
		self.style = style

	def copy(self):
		curve = Curve(self.config)
		curve.paths = deepcopy(self.paths)
		curve.style = deepcopy(self.style)
		curve.trafo = [] + self.trafo
		return curve

	def get_initial_paths(self):
		return self.paths

	def to_curve(self): return self

class Text(PrimitiveObject):

	cid = TEXT_BLOCK
	text = ""
	width = -1
	attributes = []

	def __init__(self, config, parent=None,
				rect=[] + const.STUB_RECT,
				text="",
				width=const.TEXTBLOCK_WIDTH,
				trafo=[] + const.NORMAL_TRAFO,
				style=[] + const.EMPTY_STYLE):

		if width == const.TEXTBLOCK_WIDTH:
			self.cid = TEXT_BLOCK
		else:
			self.cid = TEXT_COLUMN
		self.config = config
		self.parent = parent
		self.text = text
		if not text:
			self.text = config.default_text
		self.trafo = trafo
		self.width = width
		self.trafo[4] = rect[0]
		self.trafo[5] = rect[1] + rect[3]
		self.style = style
		self.attributes = []

	def copy(self):
		text = Text(self.config)
		text.cid = self.cid
		text.width = self.width
		text.trafo = [] + self.trafo
		text.text = "" + self.text
		text.attributes = deepcopy(self.attributes)
		text.style = deepcopy(self.style)
		return text

	def get_initial_paths(self):
		return libgeom.get_text_path(self.text, self.width, self.style,
										self.attributes)


CID_TO_CLASS = {
	DOCUMENT: Document,

	METAINFO: None, STYLES: None, STYLE: None,
	PROFILES: None, PROFILE: None, FONTS: None,
	FONT: None, IMAGES: None, IMAGE: None,

	PAGES: Pages, PAGE: Page, LAYER_GROUP: LayerGroup,
	MASTER_LAYERS: MasterLayers, LAYER: Layer,
	GRID_LAYER: GridLayer, GUIDE_LAYER: GuideLayer,
	DESKTOP_LAYERS: DesktopLayers, GUIDE: Guide,

	GROUP: Group, CONTAINER: Container,

	RECTANGLE: Rectangle, CIRCLE: Circle,
	POLYGON: Polygon, CURVE: Curve, PIXMAP: Pixmap,
	TEXT_BLOCK: Text, TEXT_COLUMN: Text,
	}

CID_TO_PROPNAME = {}
