-- tkz_elements_lines.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.

-- -------------------------------------------------------------------------
--                           Lines
-- -------------------------------------------------------------------------
line = {}
function line:new(za, zb)
    local type             = 'line'
    local mid              = (za+zb)/2
    local north_pa         = rotation_ (za,math.pi/2,zb)
    local south_pa         = rotation_ (za,-math.pi/2,zb)
    local north_pb         = rotation_ (zb,-math.pi/2,za)
    local south_pb         = rotation_ (zb,math.pi/2,za)
    local west             = rotation_ (za,math.pi/2,north_pa)
    local east             = rotation_ (zb,math.pi/2,south_pb)
    local slope            = angle_normalize_(point.arg(zb-za))
    local length           = point.mod(zb-za)
    local vec              = vector : new (za,zb)
    local li = {pa          = za, 
               pb          = zb,
               north_pa    = north_pa, 
               south_pa    = south_pa,
               west        = west, 
               east        = east,
               north_pb    = north_pb, 
               south_pb    = south_pb,
               slope       = slope,
               mid         = mid, 
               type        = type, 
               vec         = vec,
               length      = length}
    setmetatable(li, self)
    self.__index = self
    return li
end

-------------------
-- Result -> real
-------------------
function line:distance(pt)   
    return point.mod(projection(self,pt)-pt)
end

function line:slope()
   return slope_(self.pa,self.pb)
end
-------------------
-- Result -> boolean
-------------------
function line:in_out(pt)
    return math.abs((pt - self.pa) ^ (pt - self.pb)) <= tkz_epsilon
end

function line:in_out_segment(pt)
    return point.mod(pt - self.pa) + point.mod(pt - self.pb) - point.mod(self.pb - self.pa) <= tkz_epsilon
end

function line:is_parallel(L)
    return math.abs(self.slope - L.slope) < tkz_epsilon
end

function line:is_orthogonal(L)
 local alpha = angle_between_vectors(self.pa, self.pb, L.pa, L.pb)
    return math.abs(alpha-math.pi/2) < tkz_epsilon
end

function line:is_equidistant(p)
    return math.abs( (point.mod(self.pa-p)-(point.mod(self.pb-p)))) < tkz_epsilon
end
-------------------
-- Result -> point
-------------------
function line:barycenter(ka,kb)
    return barycenter_({self.pa,ka},{self.pb,kb})
end

function line:point(t) --  t=o A  t=1 B  t = AM / AB 
    return barycenter_({self.pa,1-t},{self.pb,(t)})
end

function line:midpoint()
    return (self.pa+self.pb)/2
end

function line:harmonic_int(pt)
    return div_harmonic_int_(self.pa,self.pb,pt)
end

function line:harmonic_ext(pt)
    return div_harmonic_ext_(self.pa,self.pb,pt)
end

function line:harmonic_both(k)
    return div_harmonic_both_(self.pa,self.pb,k)
end

function line:gold_ratio()
   return self.pa + (self.pb-self.pa)*tkzinvphi
end

function line:normalize()
   return  self.pa+(self.pb-self.pa)/point.mod(self.pb-self.pa)
end

function line:normalize_inv()
   return normalize_ (self.pb,self.pa)
end

function line:_east(d)
   local d = d or 1
   return self.pb+ d/self.length * (self.pb-self.pa)
end

function line:_west(d)
   local d = d or 1
   return self.pa+ d/self.length * (self.pa-self.pb)
end

function line:_north_pa(d)
   local d = d or 1
   return d/self.length * ( self.north_pa - self.pa ) + self.pa
end

function line:_south_pa(d)
   local d = d or 1
   return d/self.length *( self.south_pa - self.pa ) + self.pa
end

function line:_south_pb(d)
   local d = d or 1
   return d/self.length *( self.south_pb - self.pb ) + self.pb
end

function line:_north_pb(d)
   local d = d or 1
   return d/self.length *( self.north_pb - self.pb ) + self.pb
end

function line:report(d, pt)
    if not self.length or self.length == 0 then
        error("self.length must be non-zero")
    end
    local t = d / self.length
    local result = barycenter_({self.pa, 1 - t}, {self.pb, t})
    if pt then
        return result + pt - self.pa
    else
        return result
    end
end


function line:colinear_at (pt,k)
   if k == nil  
      then  
         return colinear_at_ (self.pa,self.pb,pt,1)
      else 
         return colinear_at_ (self.pa,self.pb,pt,k)
      end
end

-- function line:occs (pt)
--   return occs_(pt,self.pa,self.pb)
-- end
-------------- transformations -------------
function line:translation_pt( pt )
    return translation_ ( self.pb-self.pa,pt )
end

function line:translation_C( obj )
  local pa = obj.center
  local pb = obj.through
  local x,y = set_translation_( self.pb-self.pa,pa,pb )
   return circle : new  (x,y)
end

function line: translation_T( obj )
  local pa    = obj.pa
  local pb    = obj.pb
  local pc    = obj.pc
  local x,y,z = set_translation_( self.pb-self.pa,pa,pb,pc )
   return triangle : new  (x,y,z)
end

function line: translation_L( obj )
  local pa = obj.pa
  local pb = obj.pb
  local x,y = set_translation_ ( self.pb-self.pa,pa,pb )
   return line : new  (x,y)
end

function line:translation(...)
 local  tp = table.pack(...)
 local  obj = tp[1]
 local  nb = tp.n

   -- If only one object is passed
   if nb == 1 then
      if obj.type == "point" then
         return translation_(self.pb - self.pa, obj)  -- Translate point
      elseif obj.type == "line" then
         return self:translation_L(obj)  -- Translate line
      elseif obj.type == "triangle" then
         return self:translation_T(obj)  -- Translate triangle
      elseif obj.type == "circle" then
         return self:translation_C(obj)  -- Translate circle
      else
         error("Unsupported object type for translation")
      end
   else
      -- If multiple objects are passed, translate each one
     local  t = {}
      for i = 1, nb do
         -- Translate each object using the translation vector
         table.insert(t, translation_(self.pb - self.pa, tp[i]))
      end
      return table.unpack(t)  -- Return the translated objects
   end
end


function line: set_translation ( ...)
    return set_translation_ ( self.pb-self.pa,... )
end

function line:projection(...)
   local tp = table.pack(...)
  local obj = tp[1]
  local nb = tp.n
    if nb == 1 then
          return projection_ ( self.pa, self.pb, obj )
    else
      local  t = {}
        for i=1,tp.n do
            table.insert( t , projection_ (self.pa, self.pb, tp[i])  ) 
         end
      return table.unpack ( t )      
    end
end

function line:set_projection(...)
	local tp = table.pack(...)
	local i
  local t = {}
	for i=1,tp.n do
        table.insert( t , projection_ (self.pa,self.pb , tp[i])  ) 
	end
  return table.unpack ( t )
end

function line:symmetry_axial_L( obj )
  local pa = obj.pa
  local pb = obj.pb
  local x,y = self:set_reflection(pa, pb)
   return line : new  (x,y)
end
function line:symmetry_axial_T( obj )
  local pa    = obj.pa
  local pb    = obj.pb
  local pc    = obj.pc
  local x,y,z = self:set_reflection (pa, pb, pc)
   return triangle : new  (x,y,z)
end

function line:symmetry_axial_C( obj )
  local pa = obj.center
  local pb = obj.through
  local x,y = self:set_reflection( pa,pb )
   return circle : new  (x,y)
end

function line:reflection(...)
  local tp  = table.pack(...)
  local obj = tp[1]
  local nb  = tp.n
    if nb == 1 then
       if obj.type == "point" then
          return symmetry_axial_ ( self.pa,self.pb,obj )
       elseif  obj.type == "line" then
          return self: symmetry_axial_L (obj)
       elseif obj.type == "triangle" then
          return self: symmetry_axial_T (obj)
       else
         return self: symmetry_axial_C (obj)
       end
    else
      local  t = {}
        for i=1,tp.n do
            table.insert( t , symmetry_axial_ ( self.pa,self.pb , tp[i])  ) 
         end
      return table.unpack ( t )      
    end
end

function line:set_reflection (...)
    return set_symmetry_axial_ ( self.pb,self.pa,... )
end

function line:projection_ll(...)
  local tp = table.pack(...)
  local c,d = tp[1].pa,tp[1].pb
  local obj = tp[2]
  local nb = tp.n
  if nb == 2 then
        return projection_ll_(self.pa,self.pb,c,d,obj)
      else
        local  t = {}
        for i=2,tp.n do
            table.insert( t , projection_ll_ (self.pa, self.pb,c,d, tp[i])  ) 
         end
      return table.unpack ( t )      
    end
end

function line:set_projection_ll(...)
	local tp = table.pack(...)
  local c,d = tp[1].pa,tp[1].pb
  local t = {}
	for i=2,tp.n do
        table.insert( t , projection_ll_ (self.pa,self.pb ,c,d, tp[i])  ) 
	end
  return table.unpack ( t )
end

function line:affinity(...)
  local tp = table.pack(...)
  local c,d = tp[1].pa,tp[1].pb
  local k = tp[2]
  local obj = tp[3]
  local nb = tp.n
  if nb == 3 then
       return  affinity_(self.pa,self.pb,c,d,k,obj)
       else
         local  t = {}
        for i=3,tp.n do
            table.insert( t , affinity_ (self.pa, self.pb,c,d, k,tp[i])  )
         end
       return table.unpack ( t )
     end
end

function line:set_affinity(...)
	local tp = table.pack(...)
  local c,d = tp[1].pa,tp[1].pb
  local k = tp[2]
  local t = {}
	for i=3,tp.n do
        table.insert( t , affinity_ (self.pa,self.pb ,c,d,k, tp[i])  ) 
	end
  return table.unpack ( t )
end

-------------------
-- Result -> line
-------------------
function line:ll_from( pt )
	return line : new (pt,pt+self.pb-self.pa) 
end

function line:ortho_from( pt )
	return  line : new (pt+(self.pb-self.pa)*point(0,-1),
                      pt+(self.pb-self.pa)*point(0,1))
end

function line:mediator() 
   local m = midpoint_ (self.pa,self.pb)
  return line : new (rotation_ (m,-math.pi/2,self.pb),rotation_ (m,math.pi/2,self.pb)) 
end

function line:perpendicular_bisector ()
   local  m = midpoint_ (self.pa,self.pb)
  return line : new (rotation_ (m,-math.pi/2,self.pb),rotation_ (m,math.pi/2,self.pb))
end

function line: swap_line ()
  self.pa,self.pb = self.pb,self.pa
  return line : new (self.pa,self.pb)
end
-------------------
-- Result -> circle
-------------------
function line:circle(swap)
  swap = swap or false
  if swap then
    return circle:new(self.pb,self.pa)
  else
    return circle:new(self.pa,self.pb)
  end
end

function line:circle_swap()   
    return circle : new (self.pb,self.pa)
end

function line:diameter()
   local c = midpoint_(self.pa,self.pb)
  return circle:new (c,self.pb)
end

function line:apollonius(k)
   local z1,z2,c
    z1     = barycenter_({self.pa,1},{self.pb,k})
    z2     = barycenter_({self.pa,1},{self.pb,-k})
    c      = midpoint_ (z1,z2)
  return circle : new  (c,z2)
end

function line:test(x,y) 
  
end

-- Circle tangent to a line passing through two points
-- In general, there are two solutions
function line:c_l_pp(a, b) -- a and b on the same side
  -- Initialisation
  local lab = line:new(a, b) -- Line through a and b
  local Cab = circle:diameter(a, b) -- Circle with a and b diameters
  local i = intersection(lab, self) -- Intersection with current line
 
  -- One point on the line  (a)
   if self : in_out(a)  and not self:in_out(b) 
   then
    local lmed  = lab : mediator()
    local laperp = self:ortho_from(a)
    local o = intersection(lmed,laperp)
    return circle:new(o,a),
            circle:new(o,a)
    end
      -- One point on the line  (b)
    if self:in_out(b) and not self:in_out(a) then
     local lmed  = lab:mediator()
     local laperp = self:ortho_from(b)
     local o = intersection(lmed,laperp)
     return circle:new(o,b),
            circle:new(o,b)
    end    
  -- Check: if the intersection exists and lies on the segment [a, b].
   if i  and lab:in_out_segment(i) then
    return nil, nil -- No circle is possible
   end

  -- If the current line is orthogonal to lab
  if self:is_orthogonal(lab) then
    local lmed = lab:mediator() 
    local m = midpoint(a, b) 
    local r = length(m, i) 
    local pt1 = lab:isosceles_s(r)
    local pt2 = lab:isosceles_s(r, true)
    return circle:new(pt1, a),
           circle:new(pt2, a)
  end

  -- If the two lines are parallel
  if lab:is_parallel(self) then
    local mid = midpoint(a, b) -- Midpoint of segment [a, b]
    local proj = self:projection(mid) -- Mid projection on the running line

    return circle:new(circum_center_(a, b, proj), proj),
           circle:new(circum_center_(a, b, proj), proj)
  end

  -- General case
  local t = Cab:tangent_from(i).pb
  local x, y = intersection(self, circle:new(i, t))
  return circle:new(intersection(self:ortho_from(x), lab:mediator()), x),
         circle:new(intersection(self:ortho_from(y), lab:mediator()), y)
end

-- Circle tangent to two straight lines passing through a given point
function line:c_ll_p(a, p)
  
  -- Compute the bisector of the triangle formed by self.pa, self.pb, and a
  local lbi = bisector(self.pa, self.pb, a)

  if lbi:in_out(p) then
    -- Orthogonal projection of p onto the bisector
    local lp = lbi:ortho_from(p)

    -- Intersection of line from p to its projection with self.pa and self.pb
    local i = intersection_ll_(p, lp.pb, self.pa, self.pb)

    -- Intersection points of the line with the circle defined by (i, p)
    local t1, t2 = intersection_lc_(self.pa, self.pb, i, p)

    -- Create the main line and find orthogonal projections from t1 and t2
    local lab = line:new(self.pa, self.pb)
    local x = lab:ortho_from(t1).pb
    local y = lab:ortho_from(t2).pb

    -- Return two circles based on the orthogonal projections and points t1, t2
    return circle:new(intersection_ll_(x, t1, self.pa, p), t1),
           circle:new(intersection_ll_(y, t2, self.pa, p), t2)
  else
    -- Reflection of p across the bisector
    local q = lbi : reflection (p)

    -- Compute circles from the Wallis construction
    local c1, c2 = self:c_l_pp(p, q)

    -- Return two circles with centers and points on their circumference
    return  c1,c2
  end
end



----------------------
-- Result -> triangle
----------------------
function line:equilateral(swap)
    swap = swap or false
    if swap then
        return triangle:new(self.pa, self.pb, rotation_(self.pa, -math.pi / 3, self.pb))
    else
        return triangle:new(self.pa, self.pb, rotation_(self.pa, math.pi / 3, self.pb))
    end
end

function line:isosceles(phi,swap)
  local pta,ptb
   swap = swap or false
  if swap  then 
     pta = rotation_(self.pa,-phi,self.pb)
     ptb = rotation_(self.pb,phi,self.pa)
   return triangle : new (self.pa,self.pb, intersection_ll_(self.pa,pta,self.pb,ptb ))
  else
    pta = rotation_(self.pa,phi,self.pb)
    ptb = rotation_(self.pb,-phi,self.pa)
  return triangle : new (self.pa,self.pb, intersection_ll_ (self.pa,pta,self.pb,ptb ))
end
end
line.isosceles_a = line.isosceles

function line:isosceles_s(a,swap)
     local c1,c2,pta,ptb,pt1,pt2
  c1 = circle : radius (self.pa,a)
  c2 = circle : radius (self.pb,a)
  pta,ptb = intersection_cc (c1,c2)
   if get_angle(self.pa,self.pb,pta) < get_angle(self.pa,self.pb,ptb) then
     pt1=pta pt2=ptb
      else  pt1=ptb pt2=pta end  
   swap = swap or false
  if swap  then
   return triangle : new (self.pa,self.pb,pt2 )
  else
  return triangle : new (self.pa,self.pb,pt1)
end
end

function line:two_angles(alpha,beta,swap)
   local pta,ptb,pt
   swap = swap or false
   if swap  then 
     pta = rotation_(self.pa,-alpha,self.pb)
     ptb = rotation_(self.pb,beta,self.pa)
   else
   pta = rotation_(self.pa,alpha,self.pb)
   ptb = rotation_(self.pb,-beta,self.pa)
 end
   pt = intersection_ll_(self.pa,pta,self.pb,ptb)
   return triangle:new(self.pa,self.pb,pt)
end

function line:school(swap)
   local pta,ptb,pt
   swap = swap or false
   if swap  then 
     pta = rotation_(self.pa,-math.pi/6,self.pb)
     ptb = rotation_(self.pb,math.pi/3,self.pa)
   else
   pta = rotation_(self.pa,math.pi/6,self.pb)
   ptb = rotation_(self.pb,-math.pi/3,self.pa)
   end
   pt = intersection_ll_(self.pa,pta,self.pb,ptb)
   return triangle:new(self.pa,self.pb,pt)
end

function line:half(swap)
   local x,pt
   x  = midpoint_(self.pa,self.pb)
   swap = swap or false
   if swap  then 
    pt = rotation_(self.pb,math.pi/2,x)  
   else
   pt = rotation_(self.pb,-math.pi/2,x)
    end
   return triangle:new(self.pa,self.pb,pt)
end

function line:sss(a,b,swap)
   local pta,ptb,i,j
   swap = swap or false
    pta = self.pa + point ( a,  0 )
    ptb = self.pb + point ( -b , 0)
    i,j = intersection_cc_ (self.pa,pta,self.pb,ptb)
       if swap  then 
   return triangle : new (self.pa,self.pb,j)
 else 
   return triangle : new (self.pa,self.pb,i)
 end
end

function line:ssa(a, phi,swap)
    local x, y, i, j
     swap = swap or false
    x = rotation_(self.pb, -phi, self.pa)
    y = self.pa + polar_(a, self.slope)
    i, j = intersection_lc_(self.pb, x, self.pa, y)
    if swap  then  
     return  triangle:new(self.pa, self.pb, j)
    else
      return triangle:new(self.pa, self.pb, i)
    end
end

function line:sas(a, phi,swap)
    local x, pt
    swap = swap or false
    x = self.pa + polar_(a, self.slope)
     if swap  then 
        pt = rotation_(self.pa, -phi, x) 
     else
     pt = rotation_(self.pa, phi, x)
  end
   return triangle:new(self.pa, self.pb, pt)
end

function line:asa(alpha,beta,swap)
   local pta,ptb,pt
   swap = swap or false
    if swap  then 
      pta = rotation_ (self.pa,-alpha,self.pb)
      ptb = rotation_ (self.pb,beta,self.pa)
    else
   pta = rotation_ (self.pa,alpha,self.pb)
   ptb = rotation_ (self.pb,-beta,self.pa)
 end
 pt = intersection_ll_ (self.pa,pta,self.pb,ptb)
   return triangle : new (self.pa,self.pb,pt)
end
---- sacred triangles ----

function line:gold(swap)
    local pt
     swap = swap or false
     if swap  then  
        pt = rotation_ (self.pa,-math.pi/2,self.pb)
      return triangle : new (self.pa,self.pb, self.pa + (pt-self.pa) * tkzinvphi)
    else
         pt = rotation_ (self.pa,math.pi/2,self.pb)
      return triangle : new (self.pa,self.pb, self.pa + (pt-self.pa) * tkzinvphi)
    end
end

function line:sublime(swap)
   local pta,ptb,pt
   swap = swap or false
   if swap  then  
     pta = rotation_(self.pa,-2*math.pi/5,self.pb)
     ptb = rotation_(self.pb,2*math.pi/5,self.pa)
     pt = intersection_ll_(self.pa,pta,self.pb,ptb)
     return triangle:new(self.pa,self.pb,pt)
   else
  pta = rotation_(self.pa,2*math.pi/5,self.pb)
  ptb = rotation_(self.pb,-2*math.pi/5,self.pa)
  pt = intersection_ll_(self.pa,pta,self.pb,ptb)
  return triangle:new(self.pa,self.pb,pt)
end
end

line.euclid = line.sublime

function line:euclide (swap)
  swap = swap or false
  if swap  then  
     return triangle : new (self.pa,self.pb, rotation_(self.pa,-math.pi/5,self.pb))
  else
     return triangle : new (self.pa,self.pb, rotation_(self.pa,math.pi/5,self.pb))
     end
  end

  function line:divine(swap)
      local pta, ptb, pt
      swap = swap or false
      if swap  then  
        pta = rotation_(self.pa, -math.pi / 5, self.pb)
        ptb = rotation_(self.pb, math.pi / 5, self.pa)
        pt = intersection_ll_(self.pa, pta, self.pb, ptb)
        return triangle:new(self.pa, self.pb, pt)
      else
      pta = rotation_(self.pa, math.pi / 5, self.pb)
      ptb = rotation_(self.pb, -math.pi / 5, self.pa)
      pt = intersection_ll_(self.pa, pta, self.pb, ptb)
      return triangle:new(self.pa, self.pb, pt)
    end
  end

function line:cheops(swap)
    local m, n, pt
    m = midpoint_(self.pa, self.pb)
    swap = swap or false
    if swap  then 
      n = rotation_(m, math.pi / 2, self.pa)
     pt = m + (n - m) * tkzsqrtphi 
else
      n = rotation_(m, -math.pi / 2, self.pa)
     pt = m + (n - m) * tkzsqrtphi
  end
    return triangle:new(self.pa, self.pb, pt)
end


function line:egyptian(swap)
    local n, pt
    swap = swap or false
    if swap  then 
      n = rotation_(self.pb, math.pi / 2, self.pa)
      pt = self.pb + (n - self.pb) / point.mod(n - self.pb) * self.length * 0.75
   else  
    n = rotation_(self.pb, -math.pi / 2, self.pa)
    pt = self.pb + (n - self.pb) / point.mod(n - self.pb) * self.length * 0.75
  end
    return triangle:new(self.pa, self.pb, pt)
end

line.pythagoras = line.egyptian
line.isis = line.egyptian
line.golden = line.sublime
line.golden_gnomon = line.divine

------------------------------
-- Result -> square
------------------------------
function line:square (swap)
   swap = swap or false
   if swap  
   then
      return square : side (self.pa,self.pb,indirect)
   else
        return square : side (self.pa,self.pb)
   
   end
end


return line