-- | Functionality for graphing 1-dimensional vectors.
module System.Console.Ansigraph.Internal.Horizontal (
    displayRV
  , displayCV
  , displayPV
  , renderPV
  , renderRV
  , renderCV
) where

import System.Console.Ansigraph.Internal.Core

import Data.Complex
import Control.Monad.IO.Class (MonadIO)
-- for GHC <= 7.8
import Control.Applicative


---- Graphing Infrastructure  ----

barChars = "█▇▆▅▄▃▂▁ "

-- These values delineate regions for rounding to the nearest 1/8.
barVals :: [Double]
barVals = (+ 0.0625) . (/8) <$> [7,6..0]
     -- = [15/16, 13/16, 11/16, 9/16, 7/16, 5/16, 3/16, 1/16]

{- forward and reverse versions of unicode bar selection
   for positive and negative graph regions respectively -}

bars, barsR :: [(Double,Char)]
bars  = zip barVals barChars

barsR = zip barVals (reverse barChars)


selectBar, selectBarR :: Double -> Char
selectBar x = let l = filter (\p -> fst p < x) bars in
  case l of
       []     -> ' '
       (p:_) -> snd p

selectBarR x = let l = filter (\p -> fst p < x) barsR in
  case l of
       []     -> '█'
       (p:_) -> snd p


-- | Simple vector to String rendering that assumes positive input. Yields String of Unicode chars
--   representing graph bars varying in units of 1/8. The IO 'display' functions are preferable
--   for most use cases.
renderPV :: [Double] -> String
renderPV xs = let mx = maximum (filter (>= 0) $ 0:xs) in
              (selectBar . (/mx)) <$> xs

-- | Simple real vector rendering as a pair of strings. The IO 'display' functions are
--   preferable for most use cases.
renderRV :: [Double] -> (String,String)
renderRV l = let rp = l
                 rm = negate <$> rp
                 mx = maximum $ rp ++ rm
  in (selectBar  . (/mx) <$> rp,
      selectBarR . (/mx) <$> rm)

-- | Simple complex vector rendering as a pair of strings. The IO 'display' functions are
--   preferable for most use cases.
renderCV :: [Complex Double] -> (String,String,String,String)
renderCV l = let rp = realPart <$> l
                 rm = negate   <$> rp
                 ip = imagPart <$> l
                 im = negate   <$> ip
                 mx = maximum $ rp ++ rm ++ ip ++ im
  in (selectBar  . (/mx) <$> rp,
      selectBarR . (/mx) <$> rm,
      selectBar  . (/mx) <$> ip,
      selectBarR . (/mx) <$> im)


-- | ANSI based display for positive real vectors. Primarily invoked via 'graph', 'graphWith',
--   'animate', 'animateWith'.
displayPV :: MonadIO m => GraphSettings -> [Double] -> m ()
displayPV s l = let (rp,_) = renderRV l
                    rcol   = realColors s in colorStrLn rcol rp

-- | ANSI based display for real vectors. Primarily invoked via 'graph', 'graphWith',
--   'animate', 'animateWith'.
displayRV :: MonadIO m => GraphSettings -> [Double] -> m ()
displayRV s l = let (rp,rm) = renderRV l
                    rcol    = realColors s
  in do colorStrLn rcol          rp
        colorStrLn (invert rcol) rm

-- | ANSI based display for complex vectors. Primarily invoked via 'graph', 'graphWith',
--   'animate', 'animateWith'.
displayCV :: MonadIO m => GraphSettings -> [Complex Double] -> m ()
displayCV s l = let (rp,rm,ip,im) = renderCV l
                    (rcol,icol)   = colorSets s
  in do colorStrLn rcol          rp
        colorStrLn (invert rcol) rm
        colorStrLn icol          ip
        colorStrLn (invert icol) im