#
# Copyright (c) 2003, 2004, 2005 Art Haas
#
# This file is part of PythonCAD.
# 
# PythonCAD 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 2 of the License, or
# (at your option) any later version.
# 
# PythonCAD 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 PythonCAD; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# mirror operations
#

import math

from PythonCAD.Generic.point import Point
from PythonCAD.Generic.segment import Segment
from PythonCAD.Generic.circle import Circle
from PythonCAD.Generic.arc import Arc
from PythonCAD.Generic.leader import Leader
from PythonCAD.Generic.polyline import Polyline
from PythonCAD.Generic.hcline import HCLine
from PythonCAD.Generic.vcline import VCLine
from PythonCAD.Generic.acline import ACLine
from PythonCAD.Generic.cline import CLine
from PythonCAD.Generic.ccircle import CCircle

def _test_point(layer, x, y):
    _pts = layer.find('point', x, y)
    if len(_pts) == 0:
        _p = Point(x, y)
        layer.addObject(_p)
    else:
        _p = _pts[0]
    return _p

def _horizontal_mirror(hy, objlist):
    for _obj in objlist:
        _layer = _obj.getParent()
        if _layer is None:
            continue
        _mobj = None
        if isinstance(_obj, Point):
            _x, _y = _obj.getCoords()
            _ym = _y + (2.0 * (hy - _y))
            _mp = _test_point(_layer, _x, _ym)
        elif isinstance(_obj, Segment):
            _p1, _p2 = _obj.getEndpoints()
            _p1x, _p1y = _p1.getCoords()
            _p2x, _p2y = _p2.getCoords()            
            if ((abs(hy - _p1y) > 1e-10) or
                (abs(hy - _p2y) > 1e-10)):
                _y1m = _p1y + (2.0 * (hy - _p1y))
                _mp1 = _test_point(_layer, _p1x, _y1m)
                _y2m = _p2y + (2.0 * (hy - _p2y))
                _mp2 = _test_point(_layer, _p2x, _y2m)
                _mobj = _obj.clone()
                _mobj.setP1(_mp1)
                _mobj.setP2(_mp2)
                _mobj.reset()
        elif isinstance(_obj, Arc):
            _x, _y = _obj.getCenter().getCoords()
            _ym = _y + (2.0 * (hy - _y))
            _mp = _test_point(_layer, _x, _ym)
            _mobj = _obj.clone()
            _mobj.setCenter(_mp)
            _mobj.setStartAngle(360.0 - _obj.getEndAngle())
            _mobj.setEndAngle(360.0 - _obj.getStartAngle())
            _mobj.reset()
        elif isinstance(_obj, (Circle, CCircle)):
            _x, _y = _obj.getCenter().getCoords()
            if abs(hy - _y) > 1e-10:
                _ym = _y + (2.0 * (hy - _y))
                _mp = _test_point(_layer, _x, _ym)
                _mobj = _obj.clone()
                _mobj.setCenter(_mp)
                _mobj.reset()
        elif isinstance(_obj, HCLine):
            _x, _y = _obj.getLocation().getCoords()
            if abs(hy - _y) > 1e-10:
                _ym = _y + (2.0 * (hy - _y))
                _mp = _test_point(_layer, _x, _ym)
                _mobj = _obj.clone()
                _mobj.setLocation(_mp)
                _mobj.reset()
        elif isinstance(_obj, VCLine):
            pass
        elif isinstance(_obj, ACLine):
            _angle = _obj.getAngle()
            if abs(abs(_angle) - 90.0) > 1e-10: # not vertical
                _x, _y = _obj.getLocation().getCoords()
                _ym = _y + (2.0 * (hy - _y))
                _mp = _test_point(_layer, _x, _ym)
                _mobj = _obj.clone()
                if abs(_angle) > 1e-10: # not horizontal
                    _mobj.setAngle(-1.0 * _angle)
                _mobj.setLocation(_mp)
                _mobj.reset()
        elif isinstance(_obj, CLine):
            _p1, _p2 = _obj.getKeypoints()
            _x1, _y1 = _p1.getCoords()
            _x2, _y2 = _p2.getCoords()
            if abs(_x2 - _x1) > 1e-10: # not vertical
                _ym = _y1 + (2.0 * (hy - _y1))
                _mp1 = _test_point(_layer, _x1, _ym)
                _ym = _y2 + (2.0 * (hy - _y2))
                _mp2 = _test_point(_layer, _x2, _ym)
                _mobj = CLine(_mp1, _mp2)
        elif isinstance(_obj, Leader):
            _p1, _p2, _p3 = _obj.getPoints()
            _p1x, _p1y = _p1.getCoords()
            _p2x, _p2y = _p2.getCoords()
            _p3x, _p3y = _p3.getCoords()
            if (abs(_p1y - hy) > 1e-10 or
                abs(_p2y - hy) > 1e-10 or
                abs(_p3y - hy) > 1e-10):
                _ym = _p1y + (2.0 * (hy - _p1y))
                _mp1 = _test_point(_layer, _p1x, _ym)
                _ym = _p2y + (2.0 * (hy - _p2y))
                _mp2 = _test_point(_layer, _p2x, _ym)
                _ym = _p3y + (2.0 * (hy - _p3y))
                _mp3 = _test_point(_layer, _p3x, _ym)
                _mobj = _obj.clone()
                _mobj.setP1(_mp1)
                _mobj.setP2(_mp2)
                _mobj.setP3(_mp3)
        elif isinstance(_obj, Polyline):
            _mobj = _obj.clone()
            for _i in range(len(_mobj)):
                _pt = _mobj.getPoint(_i)
                _x, _y = _pt.getCoords()
                _ym = _y + (2.0 * (hy - _y))
                _mp = _test_point(_layer, _x, _ym)
                _mobj.setPoint(_i, _mp)
        else:
            print "skipping obj: " + `_obj`
        if _mobj is not None:
            _layer.addObject(_mobj)

def _vertical_mirror(vx, objlist):
    for _obj in objlist:
        _layer = _obj.getParent()
        if _layer is None:
            continue
        _mobj = None
        if isinstance(_obj, Point):
            _x, _y = _obj.getCoords()
            _xm = _x + (2.0 * (vx - _x))
            _mp = _test_point(_layer, _xm, _y)
        elif isinstance(_obj, Segment):
            _p1, _p2 = _obj.getEndpoints()
            _p1x, _p1y = _p1.getCoords()
            _p2x, _p2y = _p2.getCoords()            
            if ((abs(vx - _p1x) > 1e-10) or
                (abs(vx - _p2x) > 1e-10)):
                _x1m = _p1x + (2.0 * (vx - _p1x))
                _mp1 = _test_point(_layer, _x1m, _p1y)
                _x2m = _p2x + (2.0 * (vx - _p2x))
                _mp2 = _test_point(_layer, _x2m, _p2y)
                _mobj = _obj.clone()
                _mobj.setP1(_mp1)
                _mobj.setP2(_mp2)
                _mobj.reset()
        elif isinstance(_obj, Arc):
            _x, _y = _obj.getCenter().getCoords()
            _xm = _x + (2.0 * (vx - _x))
            _mp = _test_point(_layer, _xm, _y)
            _mobj = _obj.clone()
            _mobj.setCenter(_mp)
            _mobj.setStartAngle(180.0 - _obj.getEndAngle())
            _mobj.setEndAngle(180.0 - _obj.getStartAngle())
            _mobj.reset()
        elif isinstance(_obj, (Circle, CCircle)):
            _x, _y = _obj.getCenter().getCoords()
            if abs(vx - _x) > 1e-10:
                _xm = _x + (2.0 * (vx - _x))
                _mp = _test_point(_layer, _xm, _y)
                _mobj = _obj.clone()
                _mobj.setCenter(_mp)
                _mobj.reset()
        elif isinstance(_obj, HCLine):
            pass
        elif isinstance(_obj, VCLine):
            _x, _y = _obj.getLocation().getCoords()
            if abs(vx - _x) > 1e-10:
                _xm = _x + (2.0 * (vx - _x))
                _mp = _test_point(_layer, _xm, _y)
                _mobj = _obj.clone()
                _mobj.setLocation(_mp)
                _mobj.reset()
        elif isinstance(_obj, ACLine):
            _angle = _obj.getAngle()
            if abs(_angle) > 1e-10: # not horizontal
                _x, _y = _obj.getLocation().getCoords()
                _xm = _x + (2.0 * (vx - _x))
                _mp = _test_point(_layer, _xm, _y)
                _mobj = _obj.clone()
                if abs(abs(_angle) - 90.0) > 1e-10: # not vertical
                    _mobj.setAngle(-1.0 * _angle)
                _mobj.setLocation(_mp)
                _mobj.reset()
        elif isinstance(_obj, CLine):
            _p1, _p2 = _obj.getKeypoints()
            _x1, _y1 = _p1.getCoords()
            _x2, _y2 = _p2.getCoords()
            if abs(_y2 - _y1) > 1e-10: # not horizontal
                _xm = _x1 + (2.0 * (vx - _x1))
                _mp1 = _test_point(_layer, _xm, _y1)
                _xm = _x2 + (2.0 * (vx - _x2))
                _mp2 = _test_point(_layer, _xm, _y2)
                _mobj = CLine(_mp1, _mp2)
                _mobj.reset()
        elif isinstance(_obj, Leader):
            _p1, _p2, _p3 = _obj.getPoints()
            _p1x, _p1y = _p1.getCoords()
            _p2x, _p2y = _p2.getCoords()
            _p3x, _p3y = _p3.getCoords()
            if (abs(_p1x - vx) > 1e-10 or
                abs(_p2x - vx) > 1e-10 or
                abs(_p3x - vx) > 1e-10):
                _xm = _p1x + (2.0 * (vx - _p1x))
                _mp1 = _test_point(_layer, _xm, _p1y)
                _xm = _p2x + (2.0 * (vx - _p2x))
                _mp2 = _test_point(_layer, _xm, _p2y)
                _xm = _p3x + (2.0 * (vx - _p3x))
                _mp3 = _test_point(_layer, _xm, _p3y)
                _mobj = _obj.clone()
                _mobj.setP1(_mp1)
                _mobj.setP2(_mp2)
                _mobj.setP3(_mp3)
                _mobj.reset()
        elif isinstance(_obj, Polyline):
            _mobj = _obj.clone()
            for _i in range(len(_mobj)):
                _pt = _mobj.getPoint(_i)
                _x, _y = _pt.getCoords()
                _xm = _x + (2.0 * (vx - _x))
                _mp = _test_point(_layer, _xm, _y)
                _mobj.setPoint(_i, _mp)
            _mobj.reset()
        else:
            print "skipping obj: " + `_obj`
        if _mobj is not None:
            _layer.addObject(_mobj)

def _reflect_point(t1, t2, t3, yint, x, y):
    #
    # reflect point in coordinate frame with
    # origin at mirror line y-intersection, then
    # convert reflected point back to global
    # coordinates
    #
    _nx = (t1 * x) + (t3 * (y - yint))
    _ny = (t3 * x) + (t2 * (y - yint)) + yint
    return _nx, _ny

def _angled_mirror(slope, yint, objlist):
    _angle = math.atan(slope)
    #
    # linear algebra reflection matrix coefficients
    #
    if abs(abs(_angle) - 45.0) < 1e-10:
        _t1 = 0.0
        _t2 = 0.0
        _t3 = 1.0
    else:
        _cosine = math.cos(_angle)
        _sine = math.sin(_angle)
        _t1 = (2.0 * _cosine * _cosine) - 1.0
        _t2 = (2.0 * _sine * _sine) - 1.0
        _t3 = 2.0 * _cosine * _sine
    for _obj in objlist:
        _layer = _obj.getParent()
        if _layer is None:
            continue
        _mobj = None
        if isinstance(_obj, Point):
            _x, _y = _obj.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _np = _test_point(_layer, _rx, _ry)
        elif isinstance(_obj, Segment):
            _p1, _p2 = _obj.getEndpoints()
            _x, _y = _p1.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp1 = _test_point(_layer, _rx, _ry)
            _x, _y = _p2.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp2 = _test_point(_layer, _rx, _ry)
            _mobj = _obj.clone()
            _mobj.setP1(_mp1)
            _mobj.setP2(_mp2)
            _mobj.reset()
        elif isinstance(_obj, Arc):
            _x, _y = _obj.getCenter().getCoords()
            _rcx, _rcy = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp = _test_point(_layer, _rcx, _rcy)
            _ep1, _ep2 = _obj.getEndpoints()
            _x, _y = _ep1
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _rtd = 180.0/math.pi
            _ea = _rtd * math.atan2((_ry - _rcy), (_rx - _rcx))
            if _ea < 0.0:
                _ea = _ea + 360.0
            _x, _y = _ep2
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _sa = _rtd * math.atan2((_ry - _rcy), (_rx - _rcx))
            if _sa < 0.0:
                _sa = _sa + 360.0
            _mobj = _obj.clone()
            _mobj.setCenter(_mp)
            _mobj.setStartAngle(_sa)
            _mobj.setEndAngle(_ea)
            _mobj.reset()
        elif isinstance(_obj, (Circle, CCircle)):
            _x, _y = _obj.getCenter().getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp = _test_point(_layer, _rx, _ry)
            _mobj = _obj.clone()
            _mobj.setCenter(_mp)
            _mobj.reset()
        elif isinstance(_obj, HCLine):
            _x, _y = _obj.getLocation().getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp = _test_point(_layer, _rx, _ry)
            if abs(abs(_angle) - 45.0) < 1e-10:
                _mobj = VCLine(_mp)
            else:
                _xi = (_y - yint)/slope
                _mang = 180.0/math.pi * math.atan2((_ry - _y), (_rx - _xi))
                if _mang < 0.0:
                    _mang = _mang + 360.0
                _mobj = ACLine(_mp, _mang)
            _mobj.reset()
        elif isinstance(_obj, VCLine):
            _x, _y = _obj.getLocation().getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp = _test_point(_layer, _rx, _ry)
            if abs(abs(_angle) - 45.0) < 1e-10:
                _mobj = HCLine(_mp)
            else:
                _yi = (slope * _x) + yint 
                _mang = 180.0/math.pi * math.atan2((_ry - _yi), (_rx - _x))
                if _mang < 0.0:
                    _mang = _mang + 360.0
                _mobj = ACLine(_mp, _mang)
            _mobj.reset()
        elif isinstance(_obj, ACLine):
            _at45 = False
            if abs(abs(_angle) - 45.0) < 1e-10:
                _at45 = True
            _x, _y = _obj.getLocation().getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp = _test_point(_layer, _rx, _ry)
            _acl_angle = _obj.getAngle()
            if _at45 and abs(_acl_angle) < 1e-10:
                _mobj = VCLine(_mp)
            elif _at45 and abs(abs(_acl_angle) - 90.0) < 1e-10:
                _mobj = HCLine(_mp)
            else:
                if abs(_acl_angle) < 1e-10: # horizontal
                    _xi = (_y - yint)/slope
                    _yi = _y
                elif abs(abs(_acl_angle) - 90.0) < 1e-10: # vertical
                    _xi = _x
                    _yi = (slope * _x) + yint
                else:
                    _asl = math.tan((math.pi/180.0) * _acl_angle)
                    if abs(_asl - slope) < 1e-10: # parallel
                        _mang = _acl_angle
                    else:
                        _ayi = _y - (_asl * _x)                        
                        _xi = (_ayi - yint)/(slope - _asl)
                        _yi = (slope * _xi) + yint
                        _mang = 180.0/math.pi * math.atan2((_ry - _yi), (_rx - _xi))
                        if _mang < 0.0:
                            _mang = _mang + 360.0
                _mobj = ACLine(_mp, _mang)
            _mobj.reset()
        elif isinstance(_obj, CLine):
            _p1, _p2 = _obj.getKeypoints()
            _x, _y = _p1.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp1 = _test_point(_layer, _rx, _ry)
            _x, _y = _p2.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp2 = _test_point(_layer, _rx, _ry)
            _mobj = CLine(_mp1, _mp2)
            _mobj.reset()
        elif isinstance(_obj, Leader):
            _p1, _p2, _p3 = _obj.getPoints()
            _x, _y = _p1.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp1 = _test_point(_layer, _rx, _ry)
            _x, _y = _p2.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp2 = _test_point(_layer, _rx, _ry)
            _x, _y = _p3.getCoords()
            _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
            _mp3 = _test_point(_layer, _rx, _ry)
            _mobj = _obj.clone()
            _mobj.setP1(_mp1)
            _mobj.setP2(_mp2)
            _mobj.setP3(_mp3)
            _mobj.reset()
        elif isinstance(_obj, Polyline):
            _mpts = []
            for _i in range(len(_obj)):
                _x, _y = _obj.getPoint(_i).getCoords()
                _rx, _ry = _reflect_point(_t1, _t2, _t3, yint, _x, _y)
                _mp = _test_point(_layer, _rx, _ry)
                _mpts.append(_mp)
            _s = _obj.getStyle()
            _mobj = Polyline(_mpts, _s)
            _l = _obj.getLinetype()
            if _l != _s.getLinetype():
                _mobj.setLinetype(_l)
            _c = _obj.getColor()
            if _c != _s.getColor():
                _mobj.setColor(_c)
            _t = _obj.getThickness()
            if abs(_t - _s.getThickness()) > 1e-10:
                _mobj.setThickness(_t)
            _mobj.reset()
        else:
            print "Skipping object: " + `_obj`
        if _mobj is not None:
            _layer.addObject(_mobj)

def mirror_objects(mline, objlist):
    if isinstance(mline, HCLine):
        _x, _y = mline.getLocation().getCoords()
        _horizontal_mirror(_y, objlist)
    elif isinstance(mline, VCLine):
        _x, _y = mline.getLocation().getCoords()
        _vertical_mirror(_x, objlist)
    elif isinstance(mline, ACLine):
        _x, _y = mline.getLocation().getCoords()
        _angle = mline.getAngle()
        if abs(_angle) < 1e-10: # horizontal:
            _horizontal_mirror(_y, objlist)
        elif abs(abs(_angle) - 90.0) < 1e-10: # vertical
            _vertical_mirror(_x, objlist)
        else:
            _slope = math.tan(_angle * (math.pi/180.0))
            _yint = _y - (_slope * _x)
            _angled_mirror(_slope, _yint, objlist)
    elif isinstance(mline, CLine):
        _p1, _p2 = mline.getKeypoints()
        _p1x, _p1y = _p1.getCoords()
        _p2x, _p2y = _p2.getCoords()
        if abs(_p2y - _p1y) < 1e-10: # horizontal
            _horizontal_mirror(_p1y, objlist)
        elif abs(_p2x - _p1x) < 1e-10: # vertical
            _vertical_mirror(_p1x, objlist)
        else:
            _slope = (_p2y - _p1y)/(_p2x - _p1x)
            _yint = _p1y - (_slope * _p1x)
            _angled_mirror(_slope, _yint, objlist)
    else:
        raise TypeError, "Unexpected mirror line type: " + `type(mline)`
