-- | This module is mostly a general dump...
module Lseed.Logic where

import Lseed.Data
import Lseed.Data.Functions
import Lseed.Grammar.Parse
import Lseed.LSystem
import Lseed.Constants
import Lseed.Geometry
import Lseed.StipeInfo
import System.Time
import Text.Printf
import System.Random
import Data.List
import qualified Data.Foldable as F

timeSpanFraction :: Double -> ClockTime -> ClockTime -> Double
timeSpanFraction spanLenght (TOD sa pa) (TOD sb pb) = 
	min 1 $ max 0 $
	(fromIntegral $ (sb - sa) * 1000000000000 + (pb-pa)) /
        (spanLenght * 1000000000000 )

formatTimeInfo :: Integer -> Double -> String
formatTimeInfo day frac = let minutes = floor (frac * 12 * 60) :: Integer
			      (hour, minute) = divMod minutes 60
                          in  printf "Day %d %2d:%02d" day (6+hour) minute

-- | Given the fraction of the time passed, returnes the angle of the sunlight
lightAngle :: Double -> Angle
lightAngle diff = pi/100 + diff * (98*pi/100)

-- | Calculates the length to be grown
remainingGrowth :: (a -> GrowthState) -> Planted a -> Double
remainingGrowth getGrowths planted = go (phenotype planted)
  where go p@(Plant { pLength = l1, pBranches = ps }) =
 	   sum (map go ps) + case getGrowths (pData p) of
  		NoGrowth         -> 0
                EnlargingTo l2   -> l2 - l1
                GrowingSeed done -> (1-done) * seedGrowthCost 

-- | For a GrowingGarden, calculates the current amount of light and then
-- advance the growth. This ought to be called after applyGenome
growGarden :: (RandomGen g) => Angle -> g -> GrowingGarden -> (Double -> GrowingGarden)
growGarden angle rgen garden = sequence $ zipWith growPlanted garden totalLight
  where totalLight = map (plantTotalSum . fmap snd . phenotype) $ lightenGarden angle garden

-- | For all Growing plants that are done, find out the next step
-- If new plants are to be created, these are returned via their position, next
-- to their parent plant.
applyGenome :: (RandomGen g) => Angle -> g -> GrowingGarden -> [(GrowingPlanted,[Double])]
applyGenome angle rgen garden = zipWith applyGenome' rgens aGarden
  where rgens = unfoldr (Just . split) rgen
	aGarden = annotateGarden angle garden
	applyGenome' rgen planted =
		if   remainingGrowth siGrowth planted < eps
		then ( planted { phenotype = applyLSystem rgen
							(genome planted)
							(phenotype planted)
		     -- here, we throw away the last eps of growth. Is that a problem?
			     }
		     , collectSeeds rgen planted)
	 	else (fmap siGrowth planted,[])
	collectSeeds :: (RandomGen g) => g -> AnnotatedPlanted -> [Double]
	collectSeeds rgen planted = snd $ F.foldr go (rgen,[]) planted
	  where go si (rgen,seedPoss) = case siGrowth si of
	  		GrowingSeed _ ->
				let spread = ( - siHeight si + siOffset si
				             ,   siHeight si + siOffset si
					     )
				    (posDelta,rgen') = randomR spread rgen
				in (rgen', posDelta:seedPoss)
			_ -> (rgen,seedPoss)

-- | Applies an L-System to a Plant, putting the new length in the additional
--   information field
growPlanted :: GrowingPlanted -> Double -> (Double -> GrowingPlanted)
growPlanted planted light = 
	let remainingLength = remainingGrowth id planted
	in  if remainingLength > eps
            then let sizeOfPlant = weightedPlantLength (phenotype planted)
                     lightAvailable = light - costPerLength * sizeOfPlant
		     lowerBound = if sizeOfPlant < smallPlantBoostSize && not (doesBlossom (phenotype planted))
		                  then (1 - sizeOfPlant / smallPlantBoostSize) * smallPlantBoostLength
				  else 0
                     allowedGrowths = max lowerBound $
                                      (growthPerDayAndLight * lightAvailable) /
                                      (fromIntegral ticksPerDay) 
		     growthThisTick = min remainingLength allowedGrowths
		     growthFraction = growthThisTick / remainingLength 
		 in \tickDiff -> applyGrowth (tickDiff * growthFraction) planted
	    else const planted

doesBlossom (Plant { pData = (GrowingSeed _) }) = True
doesBlossom (Plant { pBranches = ps }) = any doesBlossom ps

-- | Applies Growth at given fraction, leaving the target length in place
applyGrowth :: Double -> GrowingPlanted -> GrowingPlanted
applyGrowth r = mapPlanted (applyGrowth' (\a b -> a * (1-r) + b * r))

applyGrowth' :: (Double -> Double -> Double) -> GrowingPlant -> GrowingPlant
applyGrowth' f = go
  where go (Plant NoGrowth l ang ut ps) = 
  		Plant NoGrowth l ang ut (map go ps)
	go (Plant (EnlargingTo l2) l1 ang ut ps) =
		Plant (EnlargingTo l2) (f l1 l2) ang ut (map go ps)
	go (Plant (GrowingSeed done) l ang ut ps) =
		Plant (GrowingSeed (f (done*seedGrowthCost) seedGrowthCost)) l ang ut (map go ps)