{-# OPTIONS_GHC -Wwarn #-}

{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

module Clash.Tests.TopEntityGeneration where

import Language.Haskell.TH.Syntax (unTypeQ)

import Test.Tasty
import Test.Tasty.HUnit

import Clash.Prelude hiding (undefined)
import Clash.Annotations.TH

type Pair a = ("left" ::: a, "right" ::: a)

data Unnamed = Unnamed Int
data Simple = Simple ("simple1" ::: Int) ("simple2" ::: Bool)
data Named
  = Named
  { name1 :: "named1" ::: BitVector 3
  , name2 :: "named2" ::: BitVector 5
  }
data Embedded
  = Embedded
  { name3 :: "embedded1" ::: Simple
  , name4 :: "embedded2" ::: Bool
  }
data OneSide
  = OneSide
  { name5 :: "embedded1" ::: Simple
  , name6 :: Bool
  }
newtype Single = Single ("s" ::: Int)

data Gadt x where
  Gadt :: ("gadt" ::: Int) -> Gadt Int

type family CF x y z where
  CF Int Int Int = ("cfiii" ::: Single)
  CF Bool Int Int = ("cfbii" ::: Single)

type family OrderedCF b f a where
    OrderedCF 'True f a = f a
    OrderedCF b f a = ()

type family OF x y z
type instance OF Int Int Int = ("ofiii" ::: Single)
type instance OF Bool Int Int = ("ofbii" ::: Single)

data family X x y z
data instance X Int Int Int = X1 ("xiii" ::: Int) ("xiii2" ::: Bool)
newtype instance X Bool Int Int = X2 ("xbii" ::: Int)

data Impossible = L ("left" ::: Int) | R ("right" ::: Bool)

data FailureTy1 = TwoF1 ("one" ::: Int) Int
data SuccessTy = TwoS ("one" ::: Int) Single

data Passthrough a b = Passthrough a b

data HKD f = HKD
    { fd1 :: OrderedCF 'True f  ("fd1" ::: Int)
    , fd2 :: OrderedCF 'True f  ("fd2" ::: Bool)
    }

data RecordWithSimOnlyField = RecordWithSimOnlyField
  { simOnlyField1 :: "a" ::: SimOnly (Signal System ("foo" ::: Int))
  , simOnlyField3 :: "b" ::: Signal System (SimOnly ("bar" ::: Int))
  , someOtherField :: "c" ::: Signal System ("wobble" ::: Int)
  }


topEntity1 :: "in1" ::: Signal System Int
           -> "in2" ::: Signal System Bool
           -> "out" ::: Signal System Int
topEntity1 = undefined
makeTopEntity 'topEntity1

expectedTopEntity1 :: TopEntity
expectedTopEntity1 =
 Synthesize "topEntity1"
    [PortName "in1", PortName "in2"]
    (PortName "out")

topEntity2 :: "int"      ::: Signal System Int
           -> "tuple"    ::: ( "tup1" ::: Signal System (BitVector 7)
                             , "tup2" ::: Signal System (BitVector 9))
           -> "simple"   ::: Signal System Simple
           -> "named"    ::: Signal System Named
           -> "embedded" ::: Signal System Embedded
           -> "out"      ::: Signal System Bool
topEntity2 = undefined
makeTopEntity 'topEntity2

expectedTopEntity2 :: TopEntity
expectedTopEntity2 =
  Synthesize "topEntity2"
    [ PortName "int"
    , PortProduct "tuple" [PortName "tup1",PortName "tup2"]
    , PortProduct "simple" [PortName "simple1",PortName "simple2"]
    , PortProduct "named" [PortName "named1",PortName "named2"]
    , PortProduct "embedded"
      [ PortProduct "embedded1"
        [ PortName "simple1"
        , PortName "simple2"]
      , PortName "embedded2"]
    ]
    (PortName "out")

topEntity3 :: "clk" ::: Clock System
           -> "rst" ::: Reset System
           -> "en"  ::: Enable System
           -> "tup1" ::: Signal System (Int, Bool)
           -> "tup2" ::: (Signal System Int, Signal System Bool)
           -> "tup3" ::: Signal System ("int":::Int, "bool":::Bool)
           -> "tup4" ::: ("int":::Signal System Int, "bool":::Signal System Bool)
           -> "custom" ::: Signal System Named
           -> "outTup" ::: Signal System ("outint":::Int, "outbool":::Bool)
topEntity3 = undefined
makeTopEntity 'topEntity3

expectedTopEntity3 :: TopEntity
expectedTopEntity3 =
  Synthesize "topEntity3"
    [ PortName "clk"
    , PortName "rst"
    , PortName "en"
    , PortName "tup1"
    , PortName "tup2"
    , PortProduct "tup3" [PortName "int",PortName "bool"]
    , PortProduct "tup4" [PortName "int",PortName "bool"]
    , PortProduct "custom" [PortName "named1",PortName "named2"]
    ]
    (PortProduct "outTup" [PortName "outint",PortName "outbool"])

topEntity4 :: Signal System (Gadt Int)
           -> Signal System Single
           -> Signal System (CF Int Int Int)
           -> Signal System (CF Bool Int Int)
           -> Signal System (OF Int Int Int)
           -> Signal System (OF Bool Int Int)
           -> Signal System (X Int Int Int)
           -> Signal System (X Bool Int Int)
           -> "hkd" ::: HKD (Signal System)
           -> Signal System Single
topEntity4 = undefined
makeTopEntity 'topEntity4

expectedTopEntity4 :: TopEntity
expectedTopEntity4 =
  Synthesize "topEntity4"
    [ PortName "gadt"
    , PortName "s"
    , PortName "cfiii_s"
    , PortName "cfbii_s"
    , PortName "ofiii_s"
    , PortName "ofbii_s"
    , PortProduct "" [PortName "xiii", PortName "xiii2"]
    , PortName "xbii"
    , PortProduct "hkd" [PortName "fd1", PortName "fd2"]
    ]
    (PortName "s")

topEntity5 :: "in1" ::: Signal System SuccessTy
           -> "ab"     ::: Signal System (Passthrough (Passthrough Simple Simple) Simple)
           -> "out" ::: Signal System Int
topEntity5 = undefined
makeTopEntity 'topEntity5

expectedTopEntity5 :: TopEntity
expectedTopEntity5 =
 Synthesize "topEntity5"
    [ PortProduct "in1" [PortName "one", PortName "s"]
    , PortProduct "ab" [PortProduct "" [PortProduct "" [PortName "simple1",PortName "simple2"]
                                       ,PortProduct "" [PortName "simple1",PortName "simple2"]]
                       ,PortProduct "" [PortName "simple1",PortName "simple2"]]
    ]
    (PortName "out")

topEntity6 :: (HiddenClockResetEnable System)
           => (1~1, Eq Int)
           => (Ord Int)
           => "in1" ::: Signal System SuccessTy
           -> "out" ::: Signal System Int
topEntity6 = undefined
makeTopEntity 'topEntity6

expectedTopEntity6 :: TopEntity
expectedTopEntity6 =
 Synthesize "topEntity6"
    [ PortProduct "" [ PortName "clk", PortName "rst", PortName "en"]
    , PortProduct "in1" [PortName "one", PortName "s"]]
    (PortName "out")


topEntity7 :: (HiddenClockResetEnable System)
           => "in1" ::: Signal System (Vec 3 Int)
           -> "in2" ::: Signal System (Vec 3 Simple)
           -> "passthrough" ::: Signal System (Passthrough Single Single)
           -> "out" ::: Signal System Int
topEntity7 = undefined
makeTopEntity 'topEntity7

expectedTopEntity7 :: TopEntity
expectedTopEntity7 =
 Synthesize "topEntity7"
    [ PortProduct "" [PortName "clk", PortName "rst", PortName "en"]
    , PortName "in1"
    , PortName "in2"
    , PortProduct "passthrough" [PortName "s", PortName "s"]
    ]
    (PortName "out")

topEntity8 :: (HiddenClockResetEnable System)
           => "pair" ::: Signal System (Pair Bool)
           -> "pair" ::: Signal System (Pair Single)
           -> "out" ::: Signal System Int
topEntity8 = undefined
makeTopEntity 'topEntity8

expectedTopEntity8 :: TopEntity
expectedTopEntity8 =
 Synthesize "topEntity8"
    [ PortProduct "" [PortName "clk", PortName "rst", PortName "en"]
    , PortProduct "pair" [PortName "left", PortName "right"]
    , PortProduct "pair" [PortName "left_s", PortName "right_s"]
    ]
    (PortName "out")

topEntity9 :: (HiddenClockResetEnable System)
           => "pair" ::: SimOnly (Signal System (Pair Bool))
           -> "pair" ::: Signal System (SimOnly (Pair Single))
           -> "out" ::: Signal System Int
topEntity9 = undefined
makeTopEntity 'topEntity9

expectedTopEntity9 :: TopEntity
expectedTopEntity9 =
 Synthesize "topEntity9"
    [ PortProduct "" [PortName "clk", PortName "rst", PortName "en"]
    , PortName "pair"
    , PortName "pair"
    ]
    (PortName "out")

topEntity10 :: (HiddenClockResetEnable System)
           => "record" ::: Signal System RecordWithSimOnlyField
           -> "out" ::: Signal System Int
topEntity10 = undefined
makeTopEntity 'topEntity10

expectedTopEntity10 :: TopEntity
expectedTopEntity10 =
 Synthesize "topEntity10"
    [ PortProduct "" [PortName "clk", PortName "rst", PortName "en"]
    , PortProduct "record" [PortName "a",PortName "b",PortName "c_wobble"]
    ]
    (PortName "out")

topEntityFailure1
  :: "int"     ::: Signal System Int
  -> "tuple"   ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9))
  -> "simple"  ::: Signal System Simple
  -> "named"   ::: Signal System Named
  -> Signal System OneSide
  -> "out"     ::: Signal System Bool
topEntityFailure1 = undefined

topEntityFailure2
  :: "int"     ::: Signal System Int
  -> "tuple"   ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9))
  -> "simple"  ::: Signal System Simple
  -> "named"   ::: Signal System Named
  -> Signal System Int
  -> "out"     ::: Signal System Bool
topEntityFailure2 = undefined

topEntityFailure3
  :: "int"     ::: Signal System Impossible
  -> "out"     ::: Signal System Bool
topEntityFailure3 = undefined

topEntityFailure4
  :: "int"     ::: Signal System FailureTy1
  -> "out"     ::: Signal System Bool
topEntityFailure4 = undefined

topEntityFailure5
  :: "int"     ::: Signal System (Passthrough (Passthrough Simple Int) Simple)
  -> "out"     ::: Signal System Bool
topEntityFailure5 = undefined

topEntityFailure6
  :: "int"     ::: Signal System a
  -> "out"     ::: Signal System Bool
topEntityFailure6 = undefined

#if MULTIPLE_HIDDEN
topEntityFailure7
  :: HiddenClockResetEnable System
  => HiddenClockResetEnable XilinxSystem
  => "int"     ::: Signal System Int
  -> "out"     ::: Signal System Bool
topEntityFailure7 = undefined
#endif

topEntityFailure8
  :: "int"     ::: Signal System (Passthrough Int Simple)
  -> "out"     ::: Signal System Bool
topEntityFailure8 = undefined

-- This splice is needed to make sure TH.names are in the type environment
-- during reify for the expected failure cases.
$( return [] )

tests :: TestTree
tests =
  testGroup "TopEntityGeneration"
    [ testGroup "Expected successes"
      [ testCase "topEntity1" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity1)
          @?= Just expectedTopEntity1
      , testCase "topEntity2" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity2)
          @?= Just expectedTopEntity2
      , testCase "topEntity3" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity3)
          @?= Just expectedTopEntity3
      , testCase "topEntity4" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity4)
          @?= Just expectedTopEntity4
      , testCase "topEntity5" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity5)
          @?= Just expectedTopEntity5
      , testCase "topEntity6" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity6)
          @?= Just expectedTopEntity6
      , testCase "topEntity7" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity7)
          @?= Just expectedTopEntity7
      , testCase "topEntity8" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity8)
          @?= Just expectedTopEntity8
      , testCase "topEntity9" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity9)
          @?= Just expectedTopEntity9
      , testCase "topEntity10" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity10)
          @?= Just expectedTopEntity10
      ]
    , testGroup "Expected failures"
      [ testCase "topEntityFailure1" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure1) @?= failed
      , testCase "topEntityFailure2" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure2) @?= failed
      , testCase "topEntityFailure3" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure3) @?= failed
      , testCase "topEntityFailure4" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure4) @?= failed
      , testCase "topEntityFailure5" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure5) @?= failed
      , testCase "topEntityFailure6" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure6) @?= failed
#if MULTIPLE_HIDDEN
      , testCase "topEntityFailure7" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure7) @?= failed
#endif
      , testCase "topEntityFailure8" $
          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntityFailure8) @?= failed
      ]
    ]
 where
  failed = Nothing :: Maybe TopEntity
