module Test.Main where

import Prelude

import Data.Array as Array
import JS.BigInt (BigInt)
import JS.BigInt as BigInt
import Data.Either (Either(..))
import Data.Foldable (intercalate)
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..), fromJust)
import Data.Newtype (class Newtype, wrap)
import Data.Proquint (Con, Proquint(..), Segment(..), Vo)
import Data.Proquint as PQ
import Data.Traversable (sequence)
import Effect (Effect)
import Effect.Aff (launchAff_)
import Partial.Unsafe (unsafePartial)
import Test.QuickCheck (class Arbitrary, arbitrary, (===))
import Test.QuickCheck.Gen (Gen, arrayOf, enum)
import Test.Spec (describe, it)
import Test.Spec.QuickCheck (quickCheck)
import Test.Spec.Reporter (consoleReporter)
import Test.Spec.Runner (runSpec)

genCon :: Gen Con
genCon = enum

genVo :: Gen Vo
genVo = enum

genSegment :: Gen Segment
genSegment = pure Segment <*> genCon <*> genVo <*> genCon <*> genVo <*> genCon

genProquint :: Gen Proquint
genProquint = pure Proquint <*> arrayOf genSegment

genProquint64 :: Gen Proquint
genProquint64 = pure Proquint <*> (sequence $ Array.replicate 4 genSegment)

genBigInt :: Gen BigInt
genBigInt =
  let
    bigintFromString = unsafePartial fromJust <<< BigInt.fromString
    boolArrayToBinary = append "0b" <<< intercalate "" <<< map bit
    bit true = "1"
    bit false = "0"
  in
    map (bigintFromString <<< boolArrayToBinary) $ sequence $ Array.replicate 64 (arbitrary @Boolean)

newtype TestCon = TestCon Con

derive instance Newtype TestCon _
derive instance Generic TestCon _
instance Arbitrary TestCon where
  arbitrary = wrap <$> enum

newtype TestVo = TestVo Vo

derive instance Newtype TestVo _
derive instance Generic TestVo _
instance Arbitrary TestVo where
  arbitrary = wrap <$> enum

newtype TestSegment = TestSegment Segment

derive instance Newtype TestSegment _
derive instance Generic TestSegment _
instance Arbitrary TestSegment where
  arbitrary = wrap <$> genSegment

newtype TestProquint = TestProquint Proquint

derive instance Newtype TestProquint _
derive instance Generic TestProquint _
instance Arbitrary TestProquint where
  arbitrary = wrap <$> genProquint

newtype TestBigInt = TestBigInt BigInt

derive instance Newtype TestBigInt _
derive instance Generic TestBigInt _
instance Arbitrary TestBigInt where
  arbitrary = wrap <$> genBigInt

main :: Effect Unit
main =
  launchAff_ $ runSpec [ consoleReporter ] do
    describe "Data.Proquint" do
      it "(toString >>> fromString) === Right"
        $ quickCheck \(TestProquint pq) -> PQ.fromString (PQ.toString pq) === Right pq
      it "(toBits >>> fromBits) === identity"
        $ quickCheck \(TestProquint pq) -> PQ.fromBits (PQ.toBits pq) === pq
      it "(fromBigInt >>> toBigInt) === Just"
        $ quickCheck \(TestBigInt bi) -> PQ.toBigInt (PQ.fromBigInt bi) === Just bi