{-# LANGUAGE BangPatterns #-}
-- | Conversion from SVG arcs to bezier curves

-- see https://github.com/GNOME/librsvg/blob/ebcbfae24321f22cd8c04a4951bbaf70b60d7f29/rust/src/path_builder.rs

module Graphics.Rasterific.Svg.ArcConversion( arcToSegments ) where

import Graphics.Svg.Types
import Linear( M22, nearZero, (!*), V2( V2 ), norm, quadrance )

toRadian :: Floating a => a -> a
toRadian v = v / 180 * pi

-- | Create a 2 dimensional rotation matrix given an angle

-- expressed in radians.

mkRotation :: Floating a => a -> M22 a
mkRotation angle =
  V2 (V2 ca (-sa))
     (V2 sa ca)
  where
    ca = cos angle
    sa = sin angle

mkRota' :: Floating a => a -> M22 a
mkRota' angle =
  V2 (V2 ca sa)
     (V2 (-sa) ca)
  where
    ca = cos angle
    sa = sin angle

arcSegment :: V2 Double -> Double -> Double -> V2 Double -> Double
           -> PathCommand
arcSegment c th0 th1 r angle = comm where
  !comm = CurveTo OriginAbsolute
    [( c + (finalRotation !* p1)
     , c + (finalRotation !* p2)
     , c + (finalRotation !* p3)
     )]

  !finalRotation = mkRotation $ toRadian angle

  !th_half = 0.5 * (th1 - th0)
  !t = (8.0 / 3.0) *
      sin (th_half * 0.5) *
      sin (th_half * 0.5) /
      sin th_half

  !cosTh0 = cos th0
  !sinTh0 = sin th0
  !cosTh1 = cos th1
  !sinTh1 = sin th1

  !p1 = r * V2 (cosTh0 - t * sinTh0) (sinTh0  + t * cosTh0)
  !p3 = r * V2 cosTh1 sinTh1
  !p2 = p3 + r * V2 (t * sinTh1) (-t * cosTh1)

-- See Appendix F.6 Elliptical arc implementation notes

-- http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes */

arc :: V2 Double -> Double -> Double -> Double -> Bool -> Bool -> V2 Double
    -> [PathCommand]
arc p1 rxOrig ryOrig x_axis_rotation is_large_arc is_sweep p2
    | p1 == p2 = mempty
    | nearZero (abs rxOrig) || nearZero (abs ryOrig) = [LineTo OriginAbsolute [p2]]
    | kCheck == 0 = mempty
    | norm kk == 0 = mempty
    | k5Norm == 0 = mempty
    | otherwise = segs
  where
    f = toRadian x_axis_rotation

    k = (p1 - p2) * 0.5
    p1_@(V2 x1_ y1_) = mkRota' f !* k

    radius@(V2 rx ry)
        | gamma > 1 = V2 (abs rxOrig * sqrt gamma) (abs ryOrig * sqrt gamma)
        | otherwise = V2 (abs rxOrig) (abs ryOrig)
      where gamma = (x1_ * x1_) / (rxOrig * rxOrig) + (y1_ * y1_) / (ryOrig * ryOrig)

    sweepCoeff | is_sweep == is_large_arc = -1
               | otherwise = 1

    -- Compute the center

    kCheck = rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_

    kc = (sweepCoeff *) . sqrt . abs $ (rx * rx * ry * ry) / kCheck - 1.0

    c_ = V2 (kc * rx * y1_ / ry) (-kc * ry * x1_ / rx)
    c = (mkRotation f !* c_) + (p1 + p2) * 0.5

    -- Compute start angle

    kk@(V2 k1 k2) = (p1_ - c_) / radius
    kkk@(V2 k3 k4) = ((-p1_) - c_) / radius

    theta1 = (if k2 < 0 then negate else id) . acos . min 1 . max (-1) $ k1 / norm kk

    -- Compute delta_theta

    k5Norm = sqrt $ quadrance kk * quadrance kkk

    delta_theta
      | is_sweep && v < 0.0 = v + 2 * pi
      | not is_sweep && v > 0.0 = v - 2 * pi
      | otherwise = v
      where
        vBase = acos . min 1 . max (-1) $ (k1 * k3 + k2 * k4) / k5Norm;
        v | k1 * k4 - k3 * k2 < 0.0 = - vBase
          | otherwise = vBase

    -- Now draw the arc

    n_segs :: Int
    n_segs = ceiling . abs $ delta_theta / (pi * 0.5 + 0.001)

    angleAt v = theta1 + fromIntegral v * delta_theta / fromIntegral n_segs

    segs =
      [arcSegment c (angleAt i) (angleAt $ i + 1) (V2 rx ry) x_axis_rotation
            | i <- [0 .. n_segs - 1]]

arcToSegments :: RPoint -> (Coord, Coord, Coord, Bool, Bool, RPoint)
              -> [PathCommand]
arcToSegments orig (radX, radY, rotateX, large, sweep, pos) =
    arc orig radX radY rotateX large sweep pos