{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}

module RotateSome where

import Utils
import Test.QuickCheck (Arbitrary, arbitrary, choose)
import XMonad.StackSet (Stack, integrate, up)
import XMonad.Actions.RotateSome (rotateSome)

newtype Divisor = Divisor Int deriving Show
instance Arbitrary Divisor where
  arbitrary = Divisor <$> choose (1, 5)

isMultOf :: Int -> Int -> Bool
x `isMultOf` n = (x `rem` n) == 0

-- Total number of elements does not change.
prop_rotate_some_length (Divisor d) (stk :: Stack Int) =
  length (integrate stk) == length (integrate $ rotateSome (`isMultOf` d) stk)

-- Applying rotateSome N times completes a cycle, where N is the number of
-- elements that satisfy the predicate.
prop_rotate_some_cycle (Divisor d) (stk :: Stack Int) =
  stk == applyN (Just n) (rotateSome (`isMultOf` d)) stk
  where
    n = length $ filter (`isMultOf` d) (integrate stk)

-- Elements that do not satisfy the predicate remain anchored in place.
prop_rotate_some_anchors (Divisor d) (stk :: Stack Int) =
  all check $
    zip
      (integrate stk)
      (integrate $ rotateSome (`isMultOf` d) stk)
  where
    check (before, after) = (before `isMultOf` d) || before == after

-- Elements that satisfy the predicate rotate by one position.
prop_rotate_some_rotate (Divisor d) (stk :: Stack Int) =
  drop 1 before ++ take 1 before == after
  where
    before = filter p (integrate stk)
    after = filter p (integrate $ rotateSome p stk)
    p = (`isMultOf` d)

-- Focus position is preserved.
prop_rotate_some_focus (Divisor d) (stk :: Stack Int) =
  length (up stk) == length (up $ rotateSome (`isMultOf` d) stk)