#
# Copyright (c) 2003, 2004 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
#
#
# functions for handling tangent circles on multiple objects
#

import math

from PythonCAD.Generic import hcline
from PythonCAD.Generic import vcline
from PythonCAD.Generic import acline
from PythonCAD.Generic import cline
from PythonCAD.Generic import ccircle

#
# common constants
#

_dtr = math.pi/180.0
_piover2 = math.pi/2.0

#
# find the projection point of point (x, y) on a line
# from (x1, y1) to (x2, y2)
#

def _get_two_point_projection(x1, y1, x2, y2, x, y):
    _sqlen = pow((x2 - x1), 2) + pow((y2 - y1), 2)
    _rn = ((x - x1) * (x2 - x1)) + ((y - y1) * (y2 - y1))
    _r = _rn/_sqlen
    _px = x1 + _r * (x2 - x1)
    _py = y1 + _r * (y2 - y1)
    return _px, _py

#
# find the projection point of point (x, y) on a line
# defined by an angle and y intercept
#

def _get_angled_projection(angle, yint, x, y):
    _x1 = 0.0
    _y1 = yint
    _x2 = _x1 + math.cos(angle)
    _y2 = _y1 + math.sin(angle)
    return _get_two_point_projection(_x1, _y1, _x2, _y2, x, y)

def _two_line_tangent(x1, y1, x2, y2, x3, y3, x4, y4, x, y):
    #
    # this function calculates the apprpriate tangent circle
    # for two lines (x1, y1)->(x2, y2) and (x3, y3)->(x4, y4)
    # using a point (x, y) to determine which tangent circle
    # should be defined
    #
    # test if lines are parallel
    #
    _cx = _cy = _radius = None
    _denom = ((x2 - x1) * (y4 - y3)) - ((y2 - y1) * (x4 - x3))
    # print "denom: %g" % _denom
    if abs(_denom) < 1e-10:
        # print "parallel ..."
        if abs(x2 - x1) < 1e-10: # both vertical
            if x < min(x1, x3) or x > max(x1, x3):
                return None
            _cx = (x3 + x1)/2.0
            _cy = y
            _radius = abs(x3 - x1)/2.0
        elif abs(y2 - y1) < 1e-10: # both horizontal
            if y < min(y1, y3) or y > max(y1, y3):
                return None
            _cx = x
            _cy = (y3 + y1)/2.0
            _radius = abs(y3 - y1)/2.0
        else: # both at equal angles
            _ax1, _ay1 = _get_two_point_projection(x1, y1, x2, y2, x, y)
            # print "ax1: %g; ay1: %g" % (_ax1, _ay1)
            _ax2, _ay2 = _get_two_point_projection(x3, y3, x4, y4, x, y)
            # print "ax2: %g; ay2: %g" % (_ax2, _ay2)
            if (x < min(_ax1, _ax2) or
                x > max(_ax1, _ax2) or
                y < min(_ay1, _ay2) or
                y > max(_ay1, _ay2)):
                return None
            _cx = (_ax1 + _ax2)/2.0
            _cy = (_ay1 + _ay2)/2.0
            _radius = math.hypot((_ax1 - _cx), (_ay1 - _cy))
        return _cx, _cy, _radius
    #
    # the lines are not parallel, so we have to test for the
    # different combinations of horizontal/vertical/sloped lines ...
    #
    if abs(y2 - y1) < 1e-10: # horizontal line 1
        # print "horizontal line 1 ..."
        if abs(y4 - y3) < 1e-10: # this should be handled above ...
            # print "horizontal line 2 ..."
            if y < min(y1, y3) or y > max(y1, y3):
                return None
            _cx = x
            _cy = (y1 + y3)/2.0
            _radius = abs(_cy - y1)
        elif abs(x4 - x3) < 1e-10: # vertical line 2
            # print "vertical line 2 ..."
            _a1 = math.pi/4.0
            _a2 = -_a1
        else:
            _angle = math.atan2((y4 - y3), (x4 - x3))
            _a1 = _angle/2.0
            if _a1 > 0.0:
                _a2 = _a1 - _piover2
            else:
                _a2 = _a1 + _piover2
    elif abs(x2 - x1) < 1e-10: # vertical line 1
        # print "vertical line 1 ..."
        if abs(y4 - y3) < 1e-10: # horizontal line2
            # print "horizontal line 2 ..."
            _a1 = math.pi/4.0
            _a2 = -_a1
        elif abs(x4 - x3) < 1e-10: # this should be handled above ...
            if x < min(x1, x3) or x > max(x1, x3):
                return None
            _cx = (x1 + x3)/2.0
            _cy = y
            _radius = abs(_cx - x1)
        else:
            _angle = math.atan2((y4 - y3), (x4 - x3))
            if _angle > 0.0:
                _a1 = (_angle + _piover2)/2.0
                _a2 = _a1 - _piover2
            else:
                _a1 = (_angle - _piover2)/2.0
                _a2 = _a1 + _piover2
    else:
        _angle1 = math.atan2((y2 - y1), (x2 - x1))
        if abs(y4 - y3) < 1e-10: # horizontal line2
            _a1 = _angle1/2.0
            if _a1 > 0.0:
                _a2 = _a1 - _piover2
            else:
                _a2 = _a1 + _piover2
        elif abs(x4 - x3) < 1e-10: # vertical line2
            if _angle1 > 0.0:
                _a1 = (_angle1 + _piover2)/2.0
                _a2 = _a1 - _piover2
            else:
                _a1 = (_angle1 - _piover2)/2.0
                _a2 = _a1 + _piover2
        else:
            _angle2 = math.atan2((y4 - y3), (x4 - x3))
            _a1 = (_angle1 + _angle2)/2.0
            if _a1 > 0.0:
                _a2 = _a1 - _piover2
            else:
                _a2 = _a1 + _piover2
    if _cx is not None and _cy is not None and _radius is not None:
        return _cx, _cy, _radius
    #
    # handle the general case of two lines at arbitrary angles
    #
    # print "arbitrary angles ..."
    # print "a1: %g" % (_a1/_dtr)
    # print "a2: %g" % (_a2/_dtr)
    _rn = ((y1 - y3) * (x4 - x3)) - ((x1 - x3) * (y4 - y3))
    # print "rn: %g" % _rn
    _r = _rn/_denom
    # print "r: %g" % _r
    _ix = x1 + _r * (x2 - x1)
    _iy = y1 + _r * (y2 - y1)
    # print "ix: %g; iy: %g" % (_ix, _iy)
    _m1 = math.tan(_a1)
    _b1 = _iy - (_m1 * _ix)
    # print "line1: m: %g; b: %g" % (_m1, _b1)
    _m2 = math.tan(_a2)
    _b2 = _iy - (_m2 * _ix)
    # print "line2: m: %g; b: %g" %  (_m2, _b2)
    _px1, _py1 = _get_angled_projection(_a1, _b1, x, y)
    # print "px1: %g; py1: %g" % (_px1, _py1)
    _sep1 = math.hypot((_px1 - x), (_py1 - y))
    _px2, _py2 = _get_angled_projection(_a2, _b2, x, y)
    # print "px2: %g; py2: %g" % (_px2, _py2)
    _sep2 = math.hypot((_px2 - x), (_py2 - y))
    if _sep1 < _sep2:
        _cx = _px1
        _cy = _py1
    else:
        _cx = _px2
        _cy = _py2
    _px, _py = _get_two_point_projection(x1, y1, x2, y2, _cx, _cy)
    _radius = math.hypot((_px - _cx), (_py - _cy))
    return _cx, _cy, _radius

#
# horizontal construction line tangents
#

def _hcl_hcl_tangent(hcla, hclb, x, y):
    _hx1, _hy1 = hcla.getLocation().getCoords()
    _hx2, _hy2 = hclb.getLocation().getCoords()
    if y < min(_hy1, _hy2) or y > max(_hy1, _hy2):
        return None
    _cx = x
    _cy = (_hy1 + _hy2)/2.0
    _radius = abs(_cy - _hy1)
    return _cx, _cy, _radius

def _hcl_vcl_tangent(hcl, vcl, x, y):
    _hx, _hy = hcl.getLocation().getCoords()
    _hx2 = _hx + 1.0
    _vx, _vy = vcl.getLocation().getCoords()
    _vy2 = _vy + 1.0
    return _two_line_tangent(_hx, _hy, _hx2, _hy, _vx, _vy, _vx, _vy2, x, y)

def _hcl_acl_tangent(hcl, acl, x, y):
    _hx, _hy = hcl.getLocation().getCoords()
    _hx2 = _hx + 1.0
    _ax, _ay = acl.getLocation().getCoords()
    _angle = acl.getAngle() * _dtr
    _ax2 = _ax + math.cos(_angle)
    _ay2 = _ay + math.sin(_angle)
    return _two_line_tangent(_hx, _hy, _hx2, _hy, _ax, _ay, _ax2, _ay2, x, y)

def _hcl_cl_tangent(hcl, cl, x, y):
    _hx, _hy = hcl.getLocation().getCoords()
    _hx2 = _hx + 1.0
    _p1, _p2 = cl.getKeypoints()
    _x1, _y1 = _p1.getCoords()
    _x2, _y2 = _p2.getCoords()
    return _two_line_tangent(_hx, _hy, _hx2, _hy, _x1, _y1, _x2, _y2, x, y)

#
# the HCLine-CCircle tangent circle problem has been generalized
# to solve the {VCLine/ACLine/CLine}-CCircle tangent calculation
#

def _gen_cline_ccircle_tangent(radius, hy, x):
    #
    # center of ccircle : (0,0)
    # radius of ccircle : r
    # distance between ccircle and hcl: hy
    # x-coordinate of mouse: x
    #
    # center of tangent circle : (px, py)
    #
    # projection point on hcl: (px, hy)
    #
    # Projection point outside the radius of the circle gives:
    #
    # math.hypot((px - cx), (py - cy)) == math.hypot((px - px), (py - hy)) + r
    #
    # Projection point inside the radius of the circle gives:
    #
    # math.hypot((px - cx), (py - cy)) + rt = r
    #
    # rt == radius of tangent circle
    #
    # Distance from projection point to tangent circle center
    #
    # math.hypot((px - px), (py - hy)) = rt
    #
    # lots of algebra reduces to the following two cases
    #
    _cx = x
    if hy > 0.0:
        _num = pow(x, 2) - pow(hy, 2) - (2.0 * hy * radius) - pow(radius, 2)
        _den = (-2.0 * hy) - (2.0 * radius)
    else:
        _num = pow(x, 2) - pow(hy, 2) + (2.0 * hy * radius) - pow(radius, 2)
        _den = (2.0 * radius) - (2.0 * hy)
    # print "num: %g" % _num
    # print "den: %g" % _den
    _cy = _num/_den
    _radius = abs(_cy - hy)
    return _cx, _cy, _radius

def _hcl_cc_tangent(hcl, cc, x, y):
    _hx, _hy = hcl.getLocation().getCoords()
    # print "hy: %g" % _hy
    _ccx, _ccy = cc.getCenter().getCoords()
    _rad = cc.getRadius()
    #
    # transform the coords into system where circle center is (0,0)
    #
    _sep = _hy - _ccy
    _xproj = x - _ccx
    _tcx, _tcy, _tcrad = _gen_cline_ccircle_tangent(_rad, _sep, _xproj)
    #
    # transform result back into real coordinates
    #
    _cx = _tcx + _ccx
    _cy = _tcy + _ccy
    return _cx, _cy, _tcrad

#
# vertical construction line
#

def _vcl_vcl_tangent(vcl1, vcl2, x, y):
    _vx1, _vy1 = vcl1.getLocation().getCoords()
    _vx2, _vy2 = vcl2.getLocation().getCoords()
    if x < min(_vx1, _vx2) or x > max(_vx1, _vx2):
        return None
    _cx = (_vx1 + _vx2)/2.0
    _cy = y
    _radius = abs(_cx - _vx1)
    return _cx, _cy, _radius

def _vcl_acl_tangent(vcl, acl, x, y):
    _vx, _vy = vcl.getLocation().getCoords()
    _vy2 = _vy + 1.0
    _ax, _ay = acl.getLocation().getCoords()
    _angle = acl.getAngle() * _dtr
    _ax2 = _ax + math.cos(_angle)
    _ay2 = _ay + math.sin(_angle)
    return _two_line_tangent(_vx, _vy, _vx, _vy2, _ax, _ay, _ax2, _ay2, x, y)

def _vcl_cl_tangent(hcl, cl, x, y):
    _vx, _vy = hcl.getLocation().getCoords()
    _vy2 = _vy + 1.0
    _p1, _p2 = cl.getKeypoints()
    _x1, _y1 = _p1.getCoords()
    _x2, _y2 = _p2.getCoords()
    return _two_line_tangent(_vx, _vy, _vx, _vy2, _x1, _y1, _x2, _y2, x, y)

def _vcl_cc_tangent(vcl, cc, x, y):
    _vx, _vy = vcl.getLocation().getCoords()
    # print "vx: %g" % _vx
    _ccx, _ccy = cc.getCenter().getCoords()
    _rad = cc.getRadius()
    #
    # transform the coords into system where circle center is (0,0) and
    # rotate 90 degrees
    #
    _sep = _vx - _ccx
    _xproj = y - _ccy
    _tcx, _tcy, _tcrad = _gen_cline_ccircle_tangent(_rad, _sep, _xproj)
    #
    # transform result back into real coordinates
    #
    _cx = _tcy + _ccx
    _cy = _tcx + _ccy
    return _cx, _cy, _tcrad

#
# angular construction line
#

def _acl_acl_tangent(acl1, acl2, x, y):
    _ax1, _ay1 = acl1.getLocation().getCoords()
    _angle1 = acl1.getAngle() * _dtr
    _ax2 = _ax1 + math.cos(_angle1)
    _ay2 = _ay1 + math.sin(_angle1)
    _ax3, _ay3 = acl2.getLocation().getCoords()
    _angle2 = acl2.getAngle() * _dtr
    _ax4 = _ax3 + math.cos(_angle2)
    _ay4 = _ay3 + math.sin(_angle2)
    return _two_line_tangent(_ax1, _ay1, _ax2, _ay2, _ax3, _ay3, _ax4, _ay4,
                             x, y)

def _acl_cl_tangent(acl, cl, x, y):
    _ax1, _ay1 = acl.getLocation().getCoords()
    _angle = acl.getAngle() * _dtr
    _ax2 = _ax1 + math.cos(_angle)
    _ay2 = _ay1 + math.sin(_angle)
    _p1, _p2 = cl.getKeypoints()
    _x1, _y1 = _p1.getCoords()
    _x2, _y2 = _p2.getCoords()
    return _two_line_tangent(_ax1, _ay1, _ax2, _ay2, _x1, _y1, _x2, _y2, x, y)

def _acl_cc_tangent(acl, cc, x, y):
    _ax, _ay = acl.getLocation().getCoords()
    # print "ax: %g; ay: %g" % (_ax, _ay)
    _angle = acl.getAngle()
    _ccx, _ccy = cc.getCenter().getCoords()
    _rad = cc.getRadius()
    #
    # transform the coords into the system where the circle center is (0,0)
    # and rotate so the ACLine is horizontal
    #
    _apx, _apy = acl.getProjection(_ccx, _ccy)
    _sep = math.hypot((_apx - _ccx), (_apy - _ccy))
    if abs(_angle) < 1e-10: # horizontal
        _sine = 0.0
        if _apy > _ccy: # system rotated 0.0
            _cosine = 1.0
        else: # system rotated 180.0
            _cosine = -1.0
    elif abs(abs(_angle) - 90.0) < 1e-10: # vertical
        _cosine = 0.0
        if _apx > _ccx: # system rotated 90.0
            _sine = 1.0
        else: # system rotated -90.0
            _sine = -1.0
    else:
        _angle = _piover2 - math.atan2((_apy - _ccy), (_apx - _ccx))
        _sine = math.sin(_angle)
        _cosine = math.cos(_angle)
    #
    # transform (x, y)
    #
    _tx1 = x - _ccx
    _ty1 = y - _ccy
    #
    # transform by rotating through _negative_ angle to
    # map to horizontal line
    #
    _tx = (_tx1 * _cosine) - (_ty1 * _sine)
    _ty = (_tx1 * _sine) + (_ty1 * _cosine)
    _tcx, _tcy, _tcrad = _gen_cline_ccircle_tangent(_rad, _sep, _tx)
    #
    # transform result back into real coordinates
    #
    _cx = ((_tcx * _cosine) + (_tcy * _sine)) + _ccx
    _cy = (-(_tcx * _sine) + (_tcy * _cosine)) + _ccy
    return _cx, _cy, _tcrad

#
# two-point construction line
#

def _cl_cl_tangent(cl1, cl2, x, y):
    _p1, _p2 = cl1.getKeypoints()
    _x1, _y1 = _p1.getCoords()
    _x2, _y2 = _p2.getCoords()
    _p3, _p4 = cl2.getKeypoints()
    _x3, _y3 = _p3.getCoords()
    _x4, _y4 = _p4.getCoords()
    return _two_line_tangent(_x1, _y1, _x2, _y2, _x3, _y3, _x4, _y4, x, y)

def _cl_cc_tangent(cl, cc, x, y):
    _p1, _p2 = cl.getKeypoints()
    _x1, _y1 = _p1.getCoords()
    # print "x1: %g; y1: %g" % (_x1, _y1)
    _x2, _y2 = _p2.getCoords()
    # print "x2: %g; y2: %g" % (_x2, _y2)
    _ccx, _ccy = cc.getCenter().getCoords()
    # print "ccx: %g; ccy: %g" % (_ccx, _ccy)
    _rad = cc.getRadius()
    #
    # transform the coords into the system where the circle center is (0,0)
    # and rotate so the CLine is horizontal
    #
    _apx, _apy = cl.getProjection(_ccx, _ccy)
    # print "apx: %g; apy: %g" % (_apx, _apy)
    _sep = math.hypot((_apx - _ccx), (_apy - _ccy))
    # print "sep: %g" % _sep
    #
    # use the line (ccx, ccy) to (apx, apy) to determine the
    # angular rotation
    #
    if abs(_apx - _ccx) < 1e-10: # cline is horizontal
        _sine = 0.0
        if _apy > _ccy: # system rotated 0.0
            _cosine = 1.0
        else: # system rotated 180.0
            _cosine = -1.0
    elif abs(_apy - _ccy) < 1e-10: # cline is vertical; system rotated -90.0
        _cosine = 0.0
        if _apx > _ccx: # system rotated 90.0
            _sine = 1.0
        else: # system rotated -90.0
            _sine = -1.0
    else:
        _angle = _piover2 - math.atan2((_apy - _ccy), (_apx - _ccx))
        # print "angle: %g" % _angle
        _sine = math.sin(_angle)
        _cosine = math.cos(_angle)
    # print "sin(angle): %g" % _sine
    # print "cos(angle): %g" % _cosine
    #
    # transform (x, y) into
    _tx1 = x - _ccx
    _ty1 = y - _ccy
    # print "tx1: %g; ty1: %g" % (_tx1, _ty1)
    #
    # transform by rotating through angle to
    # map to horizontal line
    #
    _tx = (_tx1 * _cosine) - (_ty1 * _sine)
    _ty = (_tx1 * _sine) + (_ty1 * _cosine)
    # print "tx: %g; ty: %g" % (_tx, _ty)
    _tcx, _tcy, _tcrad = _gen_cline_ccircle_tangent(_rad, _sep, _tx)
    #
    # transform result back into real coordinates
    #
    # print "tcx: %g: tcy: %g" % (_tcx, _tcy)
    _cx = ((_tcx * _cosine) + (_tcy * _sine)) + _ccx
    _cy = (-(_tcx * _sine) + (_tcy * _cosine)) + _ccy
    # print "cx: %g; cy %g" % (_cx, _cy)
    return _cx, _cy, _tcrad

def calc_tangent_circle(obja, objb, x, y):
    _x = x
    if not isinstance(_x, float):
        _x = float(x)
    _y = y
    if not isinstance(_y, float):
        _y = float(y)
    _tandata = None
    if isinstance(obja, hcline.HCLine):
        if isinstance(objb, hcline.HCLine):
            _tandata = _hcl_hcl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, vcline.VCLine):
            _tandata = _hcl_vcl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, acline.ACLine):
            _tandata = _hcl_acl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, cline.CLine):
            _tandata = _hcl_cl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, ccircle.CCircle):
            _tandata = _hcl_cc_tangent(obja, objb, _x, _y)
    elif isinstance(obja, vcline.VCLine):
        if isinstance(objb, hcline.HCLine):
            _tandata = _hcl_vcl_tangent(objb, obja, _x, _y)
        elif isinstance(objb, vcline.VCLine):
            _tandata = _vcl_vcl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, acline.ACLine):
            _tandata = _vcl_acl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, cline.CLine):
            _tandata = _vcl_cl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, ccircle.CCircle):
            _tandata = _vcl_cc_tangent(obja, objb, _x, _y)
    elif isinstance(obja, acline.ACLine):
        if isinstance(objb, hcline.HCLine):
            _tandata = _hcl_acl_tangent(objb, obja, _x, _y)
        elif isinstance(objb, vcline.VCLine):
            _tandata = _vcl_acl_tangent(objb, obja, _x, _y)
        elif isinstance(objb, acline.ACLine):
            _tandata = _acl_acl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, cline.CLine):
            _tandata = _acl_cl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, ccircle.CCircle):
            _tandata = _acl_cc_tangent(obja, objb, _x, _y)
    elif isinstance(obja, cline.CLine):
        if isinstance(objb, hcline.HCLine):
            _tandata = _hcl_cl_tangent(objb, obja, _x, _y)
        elif isinstance(objb, vcline.VCLine):
            _tandata = _vcl_cl_tangent(objb, obja, _x, _y)
        elif isinstance(objb, acline.ACLine):
            _tandata = _acl_cl_tangent(objb, obja, _x, _y)
        elif isinstance(objb, cline.CLine):
            _tandata = _cl_cl_tangent(obja, objb, _x, _y)
        elif isinstance(objb, ccircle.CCircle):
            _cl_cc_tangent(obja, objb, _x, _y)
    elif isinstance(obja, ccircle.CCircle):
        if isinstance(objb, hcline.HCLine):
            _tandata = _hcl_cc_tangent(objb, obja, _x, _y)
        elif isinstance(objb, vcline.VCLine):
            _tandata = _vcl_cc_tangent(objb, obja, _x, _y)
        elif isinstance(objb, acline.ACLine):
            _tandata = _acl_cc_tangent(objb, obja, _x, _y)
        elif isinstance(objb, cline.CLine):
            _tandata = _cl_cc_tangent(objb, obja, _x, _y)
        else:
            pass # CCircle/CCircle tangent circles to do later ...
    return _tandata

#
# calculate the possible tangent lines between two circles
#

def _calc_values(ax, ay, bx, by, cx, cy):
    """This function was used for debugging"""
    _den = pow((bx - ax), 2) + pow((by - ay), 2)
    _num = ((cx - ax) * (bx - ax)) + ((cy - ay) * (by - ay))
    _r = _num/_den
    # print "r: %g" % _r
    _num = ((ay - cy) * (bx - ax)) - ((ax - cx) * (by - ay))
    _s = _num/_den
    # print "s: %g" % _s
    _sep = abs(_s) * math.sqrt(_den)
    # print "sep: %g" % _sep
    # return _r, _s, _sep

def _calc_tangent_triangle(r1, r2, sep, ip):
    _sine = r1/abs(ip)
    # print "sin: %g" % _sine
    _angle = math.asin(_sine)
    # print "angle: %g" % (_angle * (180.0/math.pi))
    _tan = math.tan(_angle)
    # print "tan(angle): %g" % _tan
    _cosine = math.cos(_angle)
    # print "cos(angle): %g" % _cosine
    _tanlen = r1/_tan
    # print "tanlen: %g" % _tanlen
    if ip < 0.0: # r1 < r2 and intersection point left of r1
        assert abs(ip) > r1, "Expected ip beyond radius: %g < %g" % (ip, r1)
        _tx1 =  ip + (_tanlen * _cosine)
    else:
        _tx1 =  ip - (_tanlen * _cosine)
    # print "tx1: %g" % _tx1
    _ty1 = _tanlen * _sine
    # print "ty1: %g" % _ty1
    _dist = math.hypot(_tx1, _ty1)
    # print "dist: %g" % _dist
    assert abs(_dist - r1) < 1e-10, "Invalid tangent point for circle 1"
    _tanlen = r2/_tan
    # print "tanlen: %g" % _tanlen
    if ip < 0.0: # see above
        _tx2 = ip + (_tanlen * _cosine)
        _ty2 = _tanlen * _sine
    elif ip > (sep + r2): # only possible if r1 > r2
        _tx2 = ip - (_tanlen * _cosine)
        _ty2 = _tanlen * _sine
    else:
        _tx2 = ip + (_tanlen * _cosine)
        _ty2 = -1.0 * _tanlen * _sine
    # print "tx2: %g" % _tx2
    # print "ty2: %g" % _ty2
    _dist = math.hypot((_tx2 - sep), _ty2)
    # print "dist: %g" % _dist
    assert abs(_dist - r2) < 1e-10, "Invalid tangent point for circle 2"
    return _tx1, _ty1, _tx2, _ty2

def calc_two_circle_tangents(r1, r2, sep):
    # print "in calc_two_circle_tangents() ..."
    _r1 = r1
    if not isinstance(_r1, float):
        _r1 = float(r1)
    if not _r1 > 0.0:
        raise ValueError, "Invalid radius: %g" % _r1
    _r2 = r2
    if not isinstance(_r2, float):
        _r2 = float(r2)
    if not _r2 > 0.0:
        raise ValueError, "Invalid radius: %g" % _r2
    _sep = sep
    if not isinstance(_sep, float):
        _sep = float(sep)
    _tangents = []
    if (abs(_sep) + min(_r1, _r2)) > max(_r1, _r2): # small circle not within larger
        if abs(_r1 - _r2) < 1e-10:
            # print "same radii ..."
            _tangents.append((0.0, _r1, _sep, _r2))
            _tangents.append((0.0, -_r1, _sep, -_r2))
            if abs(_sep) > _r1 + _r2:
                _mid = _sep/2.0
                _angle = math.asin(_r1/_mid)
                _tanlen = _r1/math.tan(_angle)
                _xt = _tanlen * math.cos(_angle)
                _yt = _tanlen * math.sin(_angle)
                _tx1 = _mid - _xt
                _ty1 = _yt
                _tx2 = _mid + _xt
                _ty2 = -_yt
                _tangents.append((_tx1, _ty1, _tx2, _ty2))
                # _calc_values(_tx1, _ty1, _tx2, _ty2, 0.0, 0.0)
                # _calc_values(_tx1, _ty1, _tx2, _ty2, _sep, 0.0)
                _tangents.append((_tx1, -_ty1, _tx2, -_ty2))
                # _calc_values(_tx1, -_ty1, _tx2, -_ty2, 0.0, 0.0)
                # _calc_values(_tx1, -_ty1, _tx2, -_ty2, _sep, 0.0)
        else:
            _alpha = pow((_r1/_r2), 2)
            # print "alpha: %g" % _alpha
            _a = (1.0 - _alpha)
            # print "a: %g" % _a
            _b = (2.0 * _alpha * _sep)
            # print "b: %g" % _b
            _c = (-1.0 * _alpha * pow(_sep, 2))
            # print "c: %g" % _c
            _det = pow(_b, 2) - (4.0 * _a * _c)
            # print "det: %g" % _det
            if _det > 0.0: # can this ever be negative?
                # print "r1: %g" % _r1
                # print "r2: %g" % _r2
                # print "sep: %g" % _sep
                _denom = 2.0 * _a
                _det_sqrt = math.sqrt(_det)
                _num = (-1.0 * _b) + _det_sqrt
                _offset = _num/_denom
                # print "offset: %g" % _offset
                if (_r1 > _r2):
                    # print "r1 > r2"
                    if ((_offset > (_sep + _r2)) or
                        ((_offset > _r1) and (_offset < (_sep - _r2)))):
                        _tpts = _calc_tangent_triangle(_r1, _r2, _sep, _offset)
                        _tangents.append(_tpts)
                        _tx1, _ty1, _tx2, _ty2 = _tpts
                        # _calc_values(_tx1, _ty1, _tx2, _ty2, 0.0, 0.0)
                        # _calc_values(_tx1, _ty1, _tx2, _ty2, _sep, 0.0)
                        # _calc_values(_tx2, _ty2, _tx1, _ty1, _sep, 0.0)
                        # _calc_values(_tx2, _ty2, _tx1, _ty1, 0.0, 0.0)
                        _tpts = (_tx1, - _ty1, _tx2, -_ty2)
                        _tangents.append(_tpts)
                        # _calc_values(_tx1, -_ty1, _tx2,-_ty2, 0.0, 0.0)
                        # _calc_values(_tx1, -_ty1, _tx2, -_ty2, _sep, 0.0)
                        # _calc_values(_tx2, -_ty2, _tx1, -_ty1, _sep, 0.0)
                        # _calc_values(_tx2, -_ty2, _tx1, -_ty1, 0.0, 0.0)
                else: # _r1 < _r2
                   #  print "r1 < r2"
                    if ((_offset < -_r1) or
                        ((_offset > _r1) and (_offset < (_sep - _r2)))):
                        _tpts = _calc_tangent_triangle(_r1, _r2, _sep, _offset)
                        _tangents.append(_tpts)
                        _tx1, _ty1, _tx2, _ty2 = _tpts
                        # _calc_values(_tx1, _ty1, _tx2, _ty2, 0.0, 0.0)
                        # _calc_values(_tx1, _ty1, _tx2, _ty2, _sep, 0.0)
                        # _calc_values(_tx2, _ty2, _tx1, _ty1, _sep, 0.0)
                        # _calc_values(_tx2, _ty2, _tx1, _ty1, 0.0, 0.0)
                        _tpts = (_tx1, - _ty1, _tx2, -_ty2)
                        _tangents.append(_tpts)
                        # _calc_values(_tx1, -_ty1, _tx2,-_ty2, 0.0, 0.0)
                        # _calc_values(_tx1, -_ty1, _tx2, -_ty2, _sep, 0.0)
                        # _calc_values(_tx2, -_ty2, _tx1, -_ty1, _sep, 0.0)
                        # _calc_values(_tx2, -_ty2, _tx1, -_ty1, 0.0, 0.0)
                _num = (-1.0 * _b) - _det_sqrt
                _offset = _num/_denom
                # print "offset: %g" % _offset
                if (_r1 > _r2):
                    # print "r1 > r2"
                    if ((_offset > (_sep + _r2)) or
                        ((_offset > _r1) and (_offset < (_sep - _r2)))):
                        _tpts = _calc_tangent_triangle(_r1, _r2, _sep, _offset)
                        _tangents.append(_tpts)
                        _tx1, _ty1, _tx2, _ty2 = _tpts
                        _tpts = (_tx1, - _ty1, _tx2, -_ty2)
                        _tangents.append(_tpts)
                else: # _r1 < _r2
                    # print "r1 < r2"
                    if ((_offset < -_r1) or
                        ((_offset > _r1) and (_offset < (_sep - _r2)))):
                        _tpts = _calc_tangent_triangle(_r1, _r2, _sep, _offset)
                        _tangents.append(_tpts)
                        _tx1, _ty1, _tx2, _ty2 = _tpts
                        # _calc_values(_tx1, _ty1, _tx2, _ty2, 0.0, 0.0)
                        # _calc_values(_tx1, _ty1, _tx2, _ty2, _sep, 0.0)
                        # _calc_values(_tx2, _ty2, _tx1, _ty1, _sep, 0.0)
                        # _calc_values(_tx2, _ty2, _tx1, _ty1, 0.0, 0.0)
                        _tpts = (_tx1, - _ty1, _tx2, -_ty2)
                        _tangents.append(_tpts)
                        # _calc_values(_tx1, -_ty1, _tx2,-_ty2, 0.0, 0.0)
                        # _calc_values(_tx1, -_ty1, _tx2, -_ty2, _sep, 0.0)
                        # _calc_values(_tx2, -_ty2, _tx1, -_ty1, _sep, 0.0)
                        # _calc_values(_tx2, -_ty2, _tx1, -_ty1, 0.0, 0.0)
    return _tangents
