{-# LANGUAGE OverloadedStrings #-}

module Sound.Tidal.CoreTest where

import TestUtils
import Test.Microspec

import Prelude hiding ((<*), (*>))

import Data.Ratio
import Data.List (sort)

import Sound.Tidal.Context

run :: Microspec ()
run =
  describe "Sound.Tidal.Core" $ do
    describe "append" $ do
      it "can switch between the cycles from two pures" $ do
        (queryArc (append (pure "a") (pure "b")) (Arc 0 5)) `shouldBe`
          fmap toEvent
          [(((0,1), (0,1)), "a" :: String),
            (((1,2), (1,2)), "b"),
            (((2,3), (2,3)), "a"),
            (((3,4), (3,4)), "b"),
            (((4,5), (4,5)), "a")
          ]
    describe "cat" $ do
      it "can switch between the cycles from three pures" $ do
        queryArc (cat [pure "a", pure "b", pure "c"]) (Arc 0 5) `shouldBe`
          fmap toEvent
          [(((0,1), (0,1)), "a" :: String),
            (((1,2), (1,2)), "b"),
            (((2,3), (2,3)), "c"),
            (((3,4), (3,4)), "a"),
            (((4,5), (4,5)), "b")
          ]

    describe "fastCat" $ do
      it "can switch between the cycles from three pures inside one cycle" $ do
        it "1" $ queryArc (fastCat [pure "a", pure "b", pure "c"]) (Arc 0 1)
          `shouldBe` fmap toEvent
          [(((0,1/3),   (0,1/3)),   "a" :: String),
            (((1/3,2/3), (1/3,2/3)), "b"),
            (((2/3,1),   (2/3,1)),   "c")
          ]
        it "5/3" $ queryArc (fastCat [pure "a", pure "b", pure "c"]) (Arc 0 (5/3))
          `shouldBe` fmap toEvent
          [(((0,1/3),   (0,1/3)),   "a" :: String),
            (((1/3,2/3), (1/3,2/3)), "b"),
            (((2/3,1),   (2/3,1)),   "c"),
            (((1,4/3),   (1,4/3)),   "a"),
            (((4/3,5/3), (4/3,5/3)), "b")
          ]
      it "works with zero-length queries" $ do
        it "0" $
          queryArc (fastCat [pure "a", pure "b"]) (Arc 0 0)
            `shouldBe` fmap toEvent [(((0,0.5), (0,0)), "a" :: String)]
        it "1/3" $
          queryArc (fastCat [pure "a", pure "b"]) (Arc (1%3) (1%3))
            `shouldBe` fmap toEvent [(((0,0.5), (1%3,1%3)), "a" :: String)]

    describe "rev" $ do
      it "mirrors events" $ do
        let forward = fastCat [fastCat [pure 7, pure 8], pure 9] :: Pattern Int
            backward = fastCat [pure 9, fastCat [pure 8, pure 7]]
        -- sort the events into time order to compare them
        (sort $ queryArc (rev forward) (Arc 0 1)) `shouldBe` (sort $ queryArc (backward) (Arc 0 1))

      it "returns the original if you reverse it twice" $ do
        let x = fastCat [fastCat [pure 7, pure 8], pure 9] :: Pattern Int
        (queryArc (rev $ rev x) (Arc 0 5)) `shouldBe` (queryArc x (Arc 0 5))

    describe "compress" $ do
      it "squashes cycles to the start of a cycle" $ do
        let p = compress (0, 0.5) $ fastCat [pure 7, pure 8] :: Pattern Int
        (queryArc p (Arc 0 1)) `shouldBe` fmap toEvent
          [ (((0,0.25),  (0,0.25)),   7),
            (((0.25,0.5),(0.25,0.5)), 8)
          ]
      it "squashes cycles to the end of a cycle" $ do
        let p = compress (0.5, 1) $ fastCat [pure 7, pure 8] :: Pattern Int
        (queryArc p (Arc 0 1)) `shouldBe` fmap toEvent
          [(((0.5,0.75),  (0.5,0.75)), 7 :: Int),
           (((0.75,1),    (0.75,1)),   8)
          ]
      it "squashes cycles to the middle of a cycle" $ do
        let p = compress (0.25, 0.75) $ fastCat [pure 7, pure 8]
        (queryArc p (Arc 0 1)) `shouldBe` fmap toEvent
          [(((0.25,0.5),  (0.25,0.5)), 7 :: Int),
            (((0.5,0.75),  (0.5,0.75)), 8)
          ]

    describe "saw" $ do
      it "goes from 0 up to 1 every cycle" $ do
        it "0" $
          (queryArc saw (Arc 0 0))    `shouldBe` fmap toEvent [(((0,0), (0,0)),    0 :: Float)]
        it "0.25" $
          (queryArc saw (Arc 0.25 0.25)) `shouldBe` fmap toEvent [(((0.25,0.25), (0.25,0.25)), 0.25 :: Float)]
        it "0.5" $
          (queryArc saw (Arc 0.5 0.5))  `shouldBe` fmap toEvent [(((0.5,0.5), (0.5,0.5) ), 0.5 :: Float)]
        it "0.75" $
          (queryArc saw (Arc 0.75 0.75)) `shouldBe` fmap toEvent [(((0.75,0.75), (0.75,0.75)), 0.75 :: Float)]
      it "can be added to" $ do
        (map value $ queryArc ((+1) <$> saw) (Arc 0.5 0.5)) `shouldBe` [1.5 :: Float]
      it "works on the left of <*>" $ do
        (queryArc ((+) <$> saw <*> pure 3) (Arc 0 1))
          `shouldBe` fmap toEvent [(((0,1), (0,1)), 3 :: Float)]
      it "works on the right of <*>" $ do
        (queryArc ((fast 4 $ pure (+3)) <*> saw) (Arc 0 1))
          `shouldBe` fmap toEvent
          [(((0,0.25), (0,0.25)), 3 :: Float),
            (((0.25,0.5), (0.25,0.5)), 3.25),
            (((0.5,0.75), (0.5,0.75)), 3.5),
            (((0.75,1), (0.75,1)), 3.75)
          ]
      it "can be reversed" $ do
        it "works with whole cycles" $
          (queryArc (rev saw) (Arc 0 1))
            `shouldBe` fmap toEvent [(((0,1), (0,1)), 0.5 :: Float)]
        it "works with half cycles" $
          (queryArc (rev saw) (Arc 0 0.5))
            `shouldBe` fmap toEvent [(((0,0.5), (0,0.5)), 0.75 :: Float)]
        it "works with inset points" $
          (queryArc (rev saw) (Arc 0.25 0.25))
            `shouldBe` fmap toEvent [(((0.25,0.25), (0.25,0.25)), 0.75 :: Float)]

    describe "tri" $ do
      it "goes from 0 up to 1 and back every cycle" $ do
        comparePD (Arc 0 1)
          (struct "t*8" (tri :: Pattern Double))
          ("0 0.25 0.5 0.75 1 0.75 0.5 0.25")
      it "can be added to" $ do
        comparePD (Arc 0 1)
          (struct "t*8" $ (tri :: Pattern Double) + 1)
          ("1 1.25 1.5 1.75 2 1.75 1.5 1.25")
    describe "every" $ do
      it "`every n id` doesn't change the pattern's structure" $ do
        comparePD (Arc 0 4)
          (every 2 id $ "x/2" :: Pattern String)
          ("x/2")