-- tkz_elements-circles.lua
-- date 2025/02/26
-- version 3.32
-- Copyright 2025  Alain Matthes
-- This work may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either version 1.3
-- of this license or (at your option) any later version.
-- The latest version of this license is in
--   http://www.latex-project.org/lppl.txt
-- and version 1.3 or later is part of all distributions of LaTeX
-- version 2005/12/01 or later.
-- This work has the LPPL maintenance status “maintained”.
-- The Current Maintainer of this work is Alain Matthes.
---------------------------------------------------------------------------
--                           circles
---------------------------------------------------------------------------
circle = {}
function circle: new (c, t) -- c --> center t --> through
   local type              = 'circle'
   local ct                = line :new (c,t) 
   local opp               = antipode_ (c,t)
   local radius            = point.abs ( c - t )
   local south             = c - point (0,radius)
   local east              = c + point (radius,0)
   local north             = c + point (0,radius)
   local west              = c - point (radius,0)
   local perimeter         = 2*math.pi*radius
   local area              = 4*math.pi*radius*radius
   local cir = { center      = c, 
               through     = t, 
               ct          = ct,
               opp         = opp,
               radius      = radius,
               south       = south,
               east        = east,
               north       = north,
               west        = west,
               type        = type,
               perimeter   = perimeter,
               area        = area}
   setmetatable(cir, self)
   self.__index = self
   return cir
end
-- other definition

function circle: radius (center, radius) -- c --> center r --> radius
   return circle : new  (center, center + point( radius,  0 ) )
end

function circle: diameter (za, zb)
   return circle : new  (midpoint_(za,zb), zb )
end
-----------------------
-- boolean --
-----------------------
function circle:in_out(pt)
    return math.abs(point.abs(pt - self.center) - self.radius) < tkz_epsilon
end

function circle:in_out_disk(pt)
    return point.abs(pt - self.center) <= self.radius
end

-- new version 1.80 added 
function circle : circles_position (C)
   return circles_position_ (self.center,self.radius,C.center,C.radius)
end

function circle:is_tangent(l)
  local a, b = intersection(self, l)
  -- Checks whether the intersection produces valid points
  if not a or not b then
    return false
  end
  -- Checks whether the distance between the two intersection points is less than a given tolerance
  return (point.abs(b - a) < tkz_epsilon)
end

-----------------------
-- real --
-----------------------
function circle: power (pt)
  local d
    d = point.abs (self.center - pt)
   return   d * d - self.radius * self.radius
end
-----------------------
-- points --
-----------------------
function circle: antipode (pt)
   return 2 * self.center - pt
end

function circle: set_inversion (...)
	local tp = table.pack(...)
	local i
  local t = {}
	for i=1,tp.n do
        table.insert( t , inversion_ ( self.center,self.through, tp[i] ) ) 
	end
  return table.unpack ( t )
end

function circle: midarc (z1,z2)
   local phi = 0.5 * get_angle (self.center,z1,z2 )
   return rotation_ (self.center,phi,z1)
end

function circle: point (t)
   local phi = 2 * t * math.pi
   return rotation_ (self.center,phi,self.through) 
end

function circle: random_pt (lower, upper)
     math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) )
    local  phi = lower + math.random() * (upper - lower)
return point (self.center.re+self.radius*math.cos(phi),self.center.im+self.radius*math.sin(phi) )
end

function circle: internal_similitude (C)
  return internal_similitude_ (self.center,self.radius,C.center,C.radius)
end

function circle: external_similitude (C)
  return  external_similitude_ (self.center,self.radius,C.center,C.radius)
end

-----------------------
-- lines --
-----------------------
function circle:tangent_at(pt)
    return line:new(
        rotation_(pt,  math.pi / 2, self.center), 
        rotation_(pt, -math.pi / 2, self.center)
    )
end

 
 function circle:tangent_from(pt)
     local t1, t2 = tangent_from_(self.center, self.through, pt)
     return line:new(pt, t1), line:new(pt, t2)
 end
 
 function circle:radical_axis(C)
     local t1, t2
     if self.radius > C.radius then
         t1, t2 = radical_axis_(self.center, self.through, C.center, C.through)
     else
         t1, t2 = radical_axis_(C.center, C.through, self.center, self.through)
     end
     return line:new(t1, t2)
 end

 function circle : polar (p)
   local q = self : inversion (p)
   local qa = (p-q) : orthogonal (1): at (q)
   local qb = (q-p) : orthogonal (1): at (q)
   return line : new (qa,qb) 
 end
 
 function circle:radical_center(C1, C2)
     if C2 == nil then
         if self.radius > C1.radius then
             return radical_center_(self.center, self.through, C1.center, C1.through)
         else
             return radical_center_(C1.center, C1.through, self.center, self.through)
         end
     else
         return radical_center3(self, C1, C2)
     end
 end 

 function circle : radical_circle (C1,C2)
    local rc
     if C2 == nil then
        rc = self : radical_center (C1)
        return self : orthogonal_from (rc)
     else
        rc = self : radical_center (C1,C2)
        return C1 : orthogonal_from (rc)
     end
 end
 
 function circle:external_tangent(C)
     local i, t1, t2, k, T1, T2
     i = barycenter_({C.center, self.radius}, {self.center, -C.radius})
     t1, t2 = tangent_from_(self.center, self.through, i)
     k = point.mod((C.center - i) / (self.center - i))
     T1 = homothety_(i, k, t1)
     T2 = homothety_(i, k, t2)
     return line:new(t1, T1), line:new(t2, T2)
 end


 function circle : internal_tangent(C)
   local i,t1,t2,k,T1,T2
      i = barycenter_ ({C.center,self.radius},{self.center,C.radius})
      t1,t2 = tangent_from_ (self.center,self.through,i) 
      k = -point.mod((C.center-i)/(self.center-i))
      T1 = homothety_(i,k,t1)
      T2 = homothety_(i,k,t2)
   return line : new (t1,T1),line : new (t2,T2)
 end
 
 function circle : common_tangent(C)
    local o,s1,s2,t1,t2
   o = external_similitude_ (self.center,self.radius,C.center,C.radius)

  if  self.radius < C.radius then 
     t1,t2 = tangent_from_ (C.center,C.through,o) 
     s1,s2 = tangent_from_ (self.center,self.through,o) 
      return s1,t1,t2,s2
   else
      s1,s2 = tangent_from_ (C.center,C.through,o) 
      t1,t2 = tangent_from_ (self.center,self.through,o) 
       return s1,t1,t2,s2     
   end
 end
 -----------------------
 -- circles --
 -----------------------
function circle: orthogonal_from (pt)
    local t1,t2 = tangent_from_ (self.center,self.through,pt) 
    return circle : new (pt,t1)
end
 
function circle: orthogonal_through (pta,ptb)
    return circle : new (orthogonal_through_ (self.center,self.through,pta,ptb),pta)
 end

 function circle:inversion_L(L)
     if L:in_out(self.center) then
         return L
     else
         local p = L:projection(self.center)
         local q = inversion_(self.center, self.through, p)
         return circle:new(midpoint_(self.center, q), q)
     end
 end

 function circle:inversion_C(C)
    local p, q, x, y, X, Y
    if C:in_out(self.center) then
        p = C:antipode(self.center)
        q = inversion_(self.center, self.through, p)
        x = ortho_from_(q, self.center, p)
        y = ortho_from_(q, p, self.center)
        return line:new(x, y)
    else
        x, y = intersection_lc_(self.center, C.center, C.center, C.through)
        X = inversion_(self.center, self.through, x)
        Y = inversion_(self.center, self.through, y)
        return circle:new(midpoint_(X, Y), X)
    end
 end
 
 function circle:inversion(...)
     local tp = table.pack(...)
     local obj = tp[1]
     local nb = tp.n
     if nb == 1 then
         if obj.type == "point" then
             return inversion_(self.center, self.through, obj)
         elseif obj.type == "line" then
             return self:inversion_L(obj)
         else
             return self:inversion_C(obj)
         end
     else
         local t = {}
         for i = 1, nb do
             table.insert(t, inversion_(self.center, self.through, tp[i]))
         end
         return table.unpack(t)
     end
 end
 
function circle: draw ()
   local x, y = self.center: get ()
   local r = self.radius
   local frmt = '\\draw (%0.3f,%0.3f) circle [radius=%0.3f];'
   tex.sprint(string.format(frmt, x,y,r))
end 
 
function circle: midcircle(C)
  return midcircle_ (self,C)
end

-- -----------------------------------------------------------
-- Circle tangent to a circle passing through two points
function circle : c_c_pp(a,b)
  
  -- test If one point is inside the disk and the other is outside, there is no solution.
  if (self:in_out_disk(a) and not self:in_out_disk(b)) or  ( self:in_out_disk(b) and not self:in_out_disk(a)) then  
  tex.error("An error has occurred", {"Bad configuration. Only one point is in the disk"})
return end

-- Find the mediator of the current line
local  lab = line : new (a,b)
local  lmed = lab : mediator()

if self : is_tangent (lab) then
  local c = intersection (self,lab)
  local d = self : antipode (c)

  return circle:new (circum_circle_(a, b, d),a),
         circle:new (circum_circle_(a, b, d),a)
end 

-- pb are (AB) tgt to circle A and B equidistant of O tgt and equidistant
if lab : is_equidistant (self.center) then 
  local t1,t2 = intersection (lmed,self)
   return circle:new (circum_circle_(a, b, t1),t1),
          circle:new (circum_circle_(a, b, t2),t2)
else 
  -- Create a circumcircle passing through a, b, and a point on C
  local Cc = circle:new(circum_circle_(a, b, self.center), a)
  -- Find the intersection points of C and Cc
  local c, d = intersection(self, Cc)
  -- Create a line passing through the two intersection points
  local lcd = line:new(c, d)
  -- Find the intersection of the current line (self) with the line lcd
  local i = intersection(lab, lcd)
  -- Create tangents from the intersection point to C
  local lt, ltp = self:tangent_from(i)
  -- Get the tangent points
  local t, tp = lt.pb, ltp.pb
  -- Return two new circles tangent to C and passing through the tangent points
  return circle:new(intersection(lmed, line:new(self.center, t)), t),
         circle:new(intersection(lmed, line:new(self.center, tp)), tp)
end
end


-- Circle  tangent to two circles passing through a point
function circle : c_cc_p (C,p)
  local i = self: external_similitude (C)
  local lofcenters = line : new (self.center,C.center)
  local u1,u2 = intersection (lofcenters,self)
  local v1,v2 = intersection (lofcenters,C)
   local u1,v1 = self : common_tangent (C) 
  local o =  circum_circle_(u1,v1,p)
   local a,b = intersection_lc_(i,p,o,p)
   if (point.abs(a - b) < tkz_epsilon) then
    local li = line:new (i,p)
     return C : c_lc_p (li,a)
   else
   local q
   -- problem if p == q ?
   if (point.abs(a - p) < tkz_epsilon) then q = b else q=a end
   return C : c_c_pp (p,q) 
 end
end

-- Circle  tangent to one circle, on line and passing through a point
function circle : c_lc_p (l,p,inside)
    inside = inside or false  
  if self : in_out (p) then
   local t1 = intersection_ll_( self.north,p,l.pa,l.pb)
   local t2 = intersection_ll_( self.south,p,l.pa,l.pb)
   local l1 = l : ortho_from (t1)
   local l2 = l : ortho_from (t2)
   local o1 = intersection_ll_( self.center,p,l1.pa,l1.pb)
    local o2 = intersection_ll_( self.center,p,l2.pa,l2.pb)
    return circle:new(o1,t1),circle:new(o2,t2)
  else
    if l : in_out (p) then
  local i = l : projection (self.center)
  local lortho = l : ortho_from (p)
  local u = lortho : report (self.radius,p)
   local v = lortho : report (-self.radius,p)
   local ux,uy = mediator_ (self.center,u)
   local vx,vy = mediator_ (self.center,v)
   -- pb if c tgt l
    local o1= intersection_ll_(u,v,ux,uy)
    local o2 = intersection_ll_(u,v,vx,vy)
   return circle:new(o1,p),circle:new(o2,p)
 else  -- genral case
   local u = self.north
   local v = self.south
   local h = intersection_ll_(u,v,l.pa,l.pb)
     if inside then
     local o = circum_circle_(p,u,h)
     local q = intersection_lc_(p,v,o,p)
     return self : c_c_pp (p,q)
  else 
   local o = circum_circle_(p,v,h)
   local q = intersection_lc_(u,p,o,v)  
   return self : c_c_pp (p,q)
   end  
 end
 end
end


return circle