-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}

module Graphics.Implicit.ObjectUtil.GetBox3 (getBox3) where

import Graphics.Implicit.Definitions
import qualified Graphics.Implicit.MathUtil as MathUtil
import Data.Maybe (fromMaybe)
import qualified Data.Maybe as Maybe
import Data.VectorSpace

import  Graphics.Implicit.ObjectUtil.GetBox2 (getBox2, getDist2)

import Debug.Trace

isEmpty :: Box3 -> Bool
isEmpty = (== ((0,0,0), (0,0,0)))

outsetBox ::  -> Box3 -> Box3
outsetBox r (a,b) =
    (a ^-^ (r,r,r), b ^+^ (r,r,r))

getBox3 :: SymbolicObj3 -> Box3

-- Primitives
getBox3 (Rect3R r a b) = (a,b)

getBox3 (Sphere r ) = ((-r, -r, -r), (r,r,r))

getBox3 (Cylinder h r1 r2) = ( (-r,-r,0), (r,r,h) ) where r = max r1 r2

-- (Rounded) CSG
getBox3 (Complement3 symbObj) = 
    ((-infty, -infty, -infty), (infty, infty, infty)) where infty = 1/0

getBox3 (UnionR3 r symbObjs) = ((left-r,bot-r,inward-r), (right+r,top+r,out+r))
    where 
        boxes = map getBox3 symbObjs
        isEmpty = ( == ((0,0,0),(0,0,0)) )
        (leftbot, topright) = unzip $ filter (not.isEmpty) boxes
        (lefts, bots, ins) = unzip3 leftbot
        (rights, tops, outs) = unzip3 topright
        left = minimum lefts
        bot = minimum bots
        inward = minimum ins
        right = maximum rights
        top = maximum tops
        out = maximum outs

getBox3 (IntersectR3 r symbObjs) = 
    let 
        boxes = map getBox3 symbObjs
        (leftbot, topright) = unzip boxes
        (lefts, bots, ins) = unzip3 leftbot
        (rights, tops, outs) = unzip3 topright
        left = maximum lefts
        bot = maximum bots
        inward = maximum ins
        right = minimum rights
        top = minimum tops
        out = minimum outs
    in
        if   top   > bot 
          && right > left 
          && out   > inward
        then ((left,bot,inward),(right,top,out))
        else ((0,0,0),(0,0,0))

getBox3 (DifferenceR3 r symbObjs) = firstBox
    where
        firstBox:_ = map getBox3 symbObjs

-- Simple transforms
getBox3 (Translate3 v symbObj) =
    let
        (a,b) = getBox3 symbObj
    in
        (a^+^v, b^+^v)

getBox3 (Scale3 s symbObj) =
    let
        (a,b) = getBox3 symbObj
    in
        (s * a, s * b)

getBox3 (Rotate3 _ symbObj) = ( (-d, -d, -d), (d, d, d) )
    where
        ((x1,y1, z1), (x2,y2, z2)) = getBox3 symbObj
        d = (sqrt 3 *) $ maximum $ map abs [x1, x2, y1, y2, z1, z2]

getBox3 (Rotate3V _ v symbObj) = getBox3 (Rotate3 v symbObj)

-- Boundary mods
getBox3 (Shell3 w symbObj) =
    outsetBox (w/2) $ getBox3 symbObj

getBox3 (Outset3 d symbObj) =
    outsetBox d $ getBox3 symbObj

-- Misc
getBox3 (EmbedBoxedObj3 (obj,box)) = box

-- 2D Based
getBox3 (ExtrudeR r symbObj h) = ((x1,y1,0),(x2,y2,h))
    where
        ((x1,y1),(x2,y2)) = getBox2 symbObj

getBox3 (ExtrudeOnEdgeOf symbObj1 symbObj2) =
    let
        ((ax1,ay1),(ax2,ay2)) = getBox2 symbObj1
        ((bx1,by1),(bx2,by2)) = getBox2 symbObj2
    in
        ((bx1+ax1, by1+ax1, ay2), (bx2+ax2, by2+ax2, ay2))


getBox3 (ExtrudeRM r twist scale translate symbObj eitherh) = 
    let
        range = [0, 0.1 .. 1.0]

        ((x1,y1),(x2,y2)) = getBox2 symbObj
        (dx,dy) = (x2 - x1, y2 - y1)
        (xrange, yrange) = (map (\s -> x1+s*dx) $ range, map (\s -> y1+s*dy) $ range )

        h = case eitherh of
            Left h -> h
            Right hf -> hmax + 0.2*(hmax-hmin)
                where
                    hs = [hf (x,y) | x <- xrange, y <- yrange]
                    (hmin, hmax) = (minimum hs, maximum hs)
        
        hrange = map (h*) $ range

        sval = case scale of
            Nothing -> 1
            Just scale' -> maximum $ map (abs . scale') hrange
        
        (twistXmin, twistYmin, twistXmax, twistYmax) = case twist of
            Nothing -> (smin x1, smin y1, smax x2, smax y2)
                where
                    smin y = min y (sval * y)
                    smax y = max y (sval * y)
            Just _  -> (-d, -d, d, d)
                where d = sval * getDist2 (0,0) symbObj
        
        translate' = fromMaybe (const (0,0)) translate
        (tvalsx, tvalsy) = unzip . map (translate' . (h*)) $ hrange
        (tminx, tminy) = (minimum tvalsx, minimum tvalsy)
        (tmaxx, tmaxy) = (maximum tvalsx, maximum tvalsy)
    in
        ((twistXmin + tminx, twistYmin + tminy, 0),(twistXmax + tmaxx, twistYmax + tmaxy, h))


getBox3 (RotateExtrude _ _ (Left (xshift,yshift)) rotate symbObj) = 
    let
        ((x1,y1),(x2,y2)) = getBox2 symbObj
        g = maximum $ map abs [x1, x2, y1, y2]
        r = max x2 (x2 + xshift)
    in
        ((-r, -r, min y1 (y1 + yshift)),(r, r, max y2 (y2 + yshift)))

getBox3 (RotateExtrude rot _ (Right f) rotate symbObj) = 
    let
        ((x1,y1),(x2,y2)) = getBox2 symbObj
        (xshifts, yshifts) = unzip [f θ | θ <- [0 , rot / 10 .. rot] ]
        xmax = maximum xshifts
        ymax = maximum yshifts
        ymin = minimum yshifts
        xmax' = if xmax > 0 then xmax * 1.1 else if xmax < - x1 then 0 else xmax
        ymax' = ymax + 0.1 * (ymax - ymin)
        ymin' = ymin - 0.1 * (ymax - ymin)
        (r, z1, z2) = if either (==0) (const False) rotate
            then let
                s = maximum $ map abs [x2, y1, y2]
            in (s + xmax', s + ymin', y2 + ymax')
            else (x2 + xmax', y1 + ymin', y2 + ymax')
    in
        ((-r, -r, y1 + ymin'),(r, r, y2 + ymax'))