module Main where

import Test.Hspec
import Test.QuickCheck
import System.Console.Ansigraph
import System.Console.Ansigraph.Examples (wave)
import Data.Complex
import Data.Char (isSpace)
-- for GHC <= 7.8
import Control.Applicative



---- For horizontal graphing tests ----

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

eighths :: [Double]
eighths = (/8) <$> [0..8]

{- These two vectors have a maximum element of 8, to maintain a particular normalization,
   while displacing other entries by just under one half. Both vectors should display
   as █▇▆▅▄▃▂▁. Same idea for m1, m_hi, m_low below. -}

v_hi :: [Double]
v_hi = 8 : map (+0.48) (reverse [0..7])

v_low :: [Double]
v_low = 8 : map (\x -> x - 0.48) (reverse [0..7])

{- These functions are for testing the property that:
   simpleRender v == complement <$> simpleRenderR v-}

-- the 'complement' of a particular bar character is the one representing the negation of the
-- corresponding numeric value (when displayed with reversed foreground/background colors).
complement :: Char -> Char
complement c = match c bars (reverse bars)

match :: Eq a => a -> [a] -> [a] -> a
match x (y:ys) (z:zs) = if x == y then z else match x ys zs


rwave = realPart <$> wave

barInvert :: (String,String) -> (String,String)
barInvert (x,y)  = (complement <$> y, complement <$> x)

barInvertC :: (String,String,String,String) -> (String,String,String,String)
barInvertC (x1,y1,x2,y2) = (complement <$> y1,
                            complement <$> x1,
                            complement <$> y2,
                            complement <$> x2)

---- For matrix graphing tests ----

fourths :: [Double]
fourths = (/4) <$> [1..4]

mmap :: (a -> b) -> [[a]] -> [[b]]
mmap = map . map

m1 :: [[Double]]
m1 = [[1,2,3],[3,4,4]]

m_low :: [[Double]]
m_low = [[0.55,1.55,2.55], [2.55,3.55,4]]

m_hi :: [[Double]]
m_hi = [[1.45,2.45,3.45], [3.45,4,4]]


---- The spec ----

main :: IO ()
main = hspec $ do

  describe "renderPV" $ do

    it "maps exact multiples of 1/8 to the correct characters" $
      renderPV eighths `shouldBe` bars

    it "maps scaled multiples of 1/8 to the correct characters" $
      renderPV ((* 1337) <$> eighths) `shouldBe` bars

    it "maps values near tops of rounding regions to the correct characters" $
      renderPV v_hi `shouldBe` reverse bars

    it "maps values near bottoms of rounding regions to the correct characters" $
      renderPV v_low `shouldBe` reverse bars

    it "maps zero vectors to whitespace" $
      renderPV [0,0,0] `shouldBe` "   "

    it "maps non-positive vectors to whitespace" $
      renderPV [-1,-23,-0.02] `shouldBe` "   "

    it "maps null vectors to null strings" $
      renderPV [] `shouldBe` ""


  describe "renderRV" $ do

    it "inverts 'rwave' consistently" $
      let (p,n) = renderRV rwave
      in  renderRV (negate <$> rwave) `shouldBe` (complement <$> n, complement <$> p)

    it "inverts arbitrary vectors consistently" $
      property $ \xs -> renderRV xs == barInvert (renderRV $ negate <$> xs)

    it "maps arbitrary-dimensional zero vectors to whitespace, for positive components" $
      property $ \n -> let (p,m) = renderRV $ replicate n 0
                       in  all isSpace p

    it "maps arbitrary-dimensional zero vectors to solid bocks █, for negative components" $
      property $ \n -> let (p,m) = renderRV $ replicate n 0
                       in  all (== '█') m

  describe "renderCV" $ do

    it "inverts 'wave' consistently" $ do
      let (rp,rn,ip,im) = renderCV wave
      renderCV (negate <$> wave) `shouldBe` (complement <$> rn,
                                             complement <$> rp,
                                             complement <$> im,
                                             complement <$> ip)

    it "inverts arbitrary complex vectors consistently" $
      property $ \zs -> renderCV zs == barInvertC (renderCV $ negate <$> zs)

    it "maps arbitrary dimensional zero vectors to whitespace, for positive components" $
      property $ \n -> let (r1,_,i1,_) = renderCV $ replicate n (0.0 :+ 0.0)
                       in  all isSpace $ r1 ++ i1

    it "maps arbitrary dimensional zero vectors to solid bocks █, for negative components" $
      property $ \n -> let (_,r2,_,i2) = renderCV $ replicate n (0.0 :+ 0.0)
                       in  all (== '█') $ r2 ++ i2

    it "transforms appropriately under exchange of real and complex components" $
      property $ \v -> let v'  = (\(x :+ y) -> (y :+ x)) <$> v
                           (x1,y1,x2,y2)  = renderCV v
                           (x3,y3,x4,y4)  = renderCV v'
                       in  and [x1 == x4,
                                y1 == y4,
                                x2 == x3,
                                y2 == y3]

  describe "matShow" $ do

    it "maps exact multiples of 1/4 to the correct characters" $
      matShow [[1/4,2/4],[3/4,4/4]] `shouldBe` ["░▒", "▓█"]

    it "maps scaled multiples of 1/4 to the correct characters" $ do
      matShow (mmap (* 3097) [[1,2],[3,4]]) `shouldBe` ["░▒", "▓█"]
      matShow m1 `shouldBe` ["░▒▓", "▓██"]

    it "maps values near top of rounding regions to the correct characters" $
      matShow m_hi `shouldBe` ["░▒▓", "▓██"]

    it "maps values near bottom of rounding regions to the correct characters" $
      matShow m_low `shouldBe` ["░▒▓", "▓██"]
