module Data.DoubleWord.TH
  ( mkDoubleWord
  , mkUnpackedDoubleWord
  ) where
import GHC.Arr (Ix(..))
import Data.Ratio ((%))
import Data.Bits (Bits(..))
import Data.Word (Word8, Word16, Word32, Word64)
import Data.Int (Int8, Int16, Int32, Int64)
#if MIN_VERSION_hashable(1,2,0)
import Data.Hashable (Hashable(..), hashWithSalt)
#else
import Data.Hashable (Hashable(..), combine)
#endif
import Control.Applicative ((<$>), (<*>))
import Language.Haskell.TH hiding (match)
import Data.DoubleWord.Base
mkDoubleWord ∷ String 
             → String 
             → Strict 
             → Name   
             → String 
             → String 
             → Strict 
             → Name   
             → Strict 
             → Name   
             → [Name] 
             → Q [Dec]
mkDoubleWord un uc uhs uhn sn sc shs shn ls ln ad =
    (++) <$> mkDoubleWord' False un' uc' sn' sc' uhs (ConT uhn) ls lt ad
         <*> mkDoubleWord' True  sn' sc' un' uc' shs (ConT shn) ls lt ad
  where un' = mkName un
        uc' = mkName uc
        sn' = mkName sn
        sc' = mkName sc
        lt  = ConT ln
mkUnpackedDoubleWord ∷ String 
                     → Name   
                     → String 
                     → Name   
                     → Name   
                     → [Name] 
                     → Q [Dec]
mkUnpackedDoubleWord un uhn sn shn ln ad =
  mkDoubleWord un un Unpacked uhn sn sn Unpacked shn Unpacked ln ad
mkDoubleWord' ∷ Bool
              → Name → Name
              → Name → Name
              → Strict → Type
              → Strict → Type
              → [Name]
              → Q [Dec]
mkDoubleWord' signed tp cn otp ocn hiS hiT loS loT ad = (<$> mkRules) $ (++) $
    [ DataD [] tp [] [NormalC cn [(hiS, hiT), (loS, loT)]] ad
    , inst ''DoubleWord [tp]
        [ TySynInstD ''LoWord [tpT] loT
        , TySynInstD ''HiWord [tpT] hiT
        , funLo 'loWord (VarE lo)
        , inline 'loWord
        , funHi 'hiWord (VarE hi)
        , inline 'hiWord
        , fun 'fromHiAndLo (ConE cn)
        , inline 'fromHiAndLo
        
        , funX 'extendLo $ appWN ['allZeroes, x]
        , inline 'extendLo
        
        , funX 'signExtendLo $
            appW [ CondE (appVN 'testMsb [x])
                         (VarE 'allOnes) (VarE 'allZeroes)
                 , appVN 'unsignedWord [x] ]
        , inlinable 'signExtendLo
        ]
    , inst ''Eq [tp] $
        
        [ funHiLo2 '(==) $
            appV '(&&) [appVN '(==) [hi, hi'], appVN '(==) [lo, lo']]
        , inline '(==) ]
    , inst ''Ord [tp]
        
        [ funHiLo2 'compare $
            CaseE (appVN 'compare [hi, hi'])
              [ Match (ConP 'EQ []) (NormalB (appVN 'compare [lo, lo'])) []
              , Match (VarP x) (NormalB (VarE x)) [] ]
        , inlinable 'compare ]
    , inst ''Bounded [tp]
        
        [ fun 'minBound $ appWN ['minBound, 'minBound]
        , inline 'minBound
        
        , fun 'maxBound $ appWN ['maxBound, 'maxBound]
        , inline 'maxBound ]
    , inst ''Enum [tp]
        
        [ funHiLo 'succ $ CondE (appVN '(==) [lo, 'maxBound])
                                (appW [appVN 'succ [hi], VarE 'minBound])
                                (appW [VarE hi, appVN 'succ [lo]])
        , inlinable 'succ
        
        , funHiLo 'pred $ CondE (appVN '(==) [lo, 'minBound])
                                (appW [appVN 'pred [hi], VarE 'maxBound])
                                (appW [VarE hi, appVN 'pred [lo]])
        , inlinable 'pred
        
        , funX 'toEnum $
            CondE (appV '(<) [VarE x, litI 0])
                  (if signed
                   then appW [ VarE 'allOnes
                             , appV 'negate
                                 [ appV '(+)
                                     [ oneE
                                     , appV 'toEnum
                                         [ appV 'negate
                                             [appV '(+) [VarE x, litI 1]] ]
                                     ]
                                 ]
                             ]
                   else appV 'error [litS "toEnum: nagative value"])
                  (appW [VarE 'allZeroes, appVN 'toEnum [x]])
        
        , FunD 'fromEnum $
            Clause [ConP cn [LitP $ IntegerL 0, VarP lo]]
                   (NormalB $ appVN 'fromEnum [lo]) [] :
            if signed
            then [ Clause [ConP cn [LitP $ IntegerL (1), VarP lo]]
                          (NormalB $
                             appV 'negate
                               [appV 'fromEnum [appV 'negate [VarE lo]]])
                          []
                 , Clause [WildP]
                          (NormalB $
                             appV 'error [litS "fromEnum: out of bounds"])
                          []
                 ]
            else [ Clause [WildP]
                          (NormalB $
                             appV 'error [litS "fromEnum: out of bounds"])
                          [] ]
        
        , funX 'enumFrom $ appVN 'enumFromTo [x, 'maxBound]
        , inline 'enumFrom
        
        , funXY 'enumFromThen $
            appV 'enumFromThenTo
              [ VarE x
              , VarE y
              , CondE (appVN '(>=) [x, y]) (VarE 'maxBound) (VarE 'minBound)
              ]
        , inlinable 'enumFromThen
        
        , FunD 'enumFromTo $ return $
            Clause
              [VarP x, VarP y]
              (NormalB $
                 CaseE (appVN 'compare [y, x])
                   [ Match
                       (ConP 'LT [])
                       (NormalB $ appC '(:) [VarE x, appVN down [y, x]])
                       []
                   , Match
                       (ConP 'EQ [])
                       (NormalB $ appC '(:) [VarE x, ConE '[]])
                       []
                   , Match
                       (ConP 'GT [])
                       (NormalB $ appC '(:) [VarE x, appVN up [y, x]])
                       []
                   ])
              [ FunD down $ return $
                  Clause [VarP to, VarP c]
                    (NormalB $
                       appC '(:)
                         [ VarE next
                         , CondE (appVN '(==) [next, to])
                                 (ConE '[]) (appVN down [to, next])
                         ])
                    [ValD (VarP next)
                          (NormalB $ appVN '() [c, 'lsb]) []]
              , FunD up $ return $
                  Clause [VarP to, VarP c]
                    (NormalB $
                       appC '(:)
                         [ VarE next
                         , CondE (appVN '(==) [next, to])
                                 (ConE '[]) (appVN up [to, next])
                         ])
                    [ValD (VarP next)
                          (NormalB $ appVN '(+) [c, 'lsb]) []]
              ]
        
        , FunD 'enumFromThenTo $ return $
            Clause [VarP x, VarP y, VarP z]
              (NormalB $
                CaseE (appVN 'compare [y, x])
                  [ Match
                      (ConP 'LT [])
                      (NormalB $
                         CondE (appVN '(>) [z, x])
                               (ConE '[])
                               (appV down [appVN '() [x, y], VarE z, VarE x]))
                      []
                  , Match (ConP 'EQ []) (NormalB $ appVN 'repeat [x]) []
                  , Match
                      (ConP 'GT [])
                      (NormalB $
                         CondE (appVN '(<) [z, x]) (ConE '[])
                               (appV up [appVN '() [y, x], VarE z, VarE x]))
                      []
                  ])
              [ FunD down $ return $
                  Clause [VarP step, VarP to, VarP c]
                    (NormalB $
                       appC '(:)
                         [ VarE c
                         , CondE (appVN '(<) [next, to])
                                 (ConE '[]) (appVN down [step, to, next])
                         ])
                    [ValD (VarP next) (NormalB $ appVN '() [c, step]) []]
              , FunD up $ return $
                  Clause [VarP step, VarP to, VarP c]
                    (NormalB $
                       appC '(:)
                         [ VarE c
                         , CondE (appVN '(==) [next, to])
                                 (ConE '[]) (appVN up [step, to, next])
                         ])
                    [ValD (VarP next) (NormalB $ appVN '(+) [c, step]) []]]
        ]
    , inst ''Num [tp]
        
        [ funHiLo 'negate $
            CondE (appVN '(==) [lo, 'allZeroes])
                  (appW [appVN 'negate [hi], zeroE])
                  (appW [ appV 'negate [appVN '(+) ['lsb, hi]]
                        , appVN 'negate [lo] ])
        , inlinable 'negate
        
        , funX 'abs $
            if signed
            then CondE (appVN '(<) [x, 'allZeroes])
                       (appVN 'negate [x]) (VarE x)
            else VarE x
        , if signed then inlinable 'abs else inline 'abs
        
        , funHiLo 'signum $
            if signed
            then CaseE (appVN 'compare [hi, 'allZeroes])
                   [ Match (ConP 'LT [])
                           (NormalB $ appWN ['allOnes, 'maxBound]) []
                   , Match (ConP 'EQ [])
                           (NormalB $ CondE (appVN '(==) [lo, 'allZeroes])
                                            zeroE oneE)
                           []
                   , Match (ConP 'GT []) (NormalB oneE) []
                   ]
            else CondE (appV '(&&) [ appVN '(==) [hi, 'allZeroes]
                                   , appVN '(==) [lo, 'allZeroes] ])
                       zeroE oneE
        , inlinable 'signum
        
        , funHiLo2' '(+) (appWN [y, x])
            [ val x $ appVN '(+) [lo, lo']
            , val y $ appV '(+)
                        [ appVN '(+) [hi, hi']
                        , CondE (appVN '(<) [x, lo]) oneE zeroE ]
            ]
        , inlinable '(+)
        
        , if signed
          then
            funXY '(*) $
              appV 'signedWord
                   [appV '(*) [ appVN 'unsignedWord [x]
                              , appVN 'unsignedWord [y] ]]
          else
            funHiLo2' '(*)
              (appW [ appV '(+)
                        [ appV '(+)
                            [ appV '(*) [VarE hi, appVN 'fromIntegral [lo']]
                            , appV '(*) [VarE hi', appVN 'fromIntegral [lo]] ]
                        , appVN 'fromIntegral [x] ]
                    , VarE y ])
              [vals [x, y] (appVN 'unwrappedMul [lo, lo'])]
        , inlinable '(*)
        
        , funX' 'fromInteger
            (appW [appVN 'fromInteger [y], appVN 'fromInteger [z]])
            [vals [y, z]
               (appV 'divMod
                  [ VarE x
                  , appV '(+)
                      [appV 'toInteger [SigE (VarE 'maxBound) loT], litI 1]
                  ])]
        ]
    , inst ''Real [tp]
        
        [ funX 'toRational $ appV '(%) [appVN 'toInteger [x], litI 1]
        , inline 'toRational ]
    , inst ''Integral [tp] $
        
        [ funHiLo 'toInteger $
            appV '(+)
              [ appV '(*)
                  [ appVN 'toInteger [hi]
                  , appV '(+)
                      [appV 'toInteger [SigE (VarE 'maxBound) loT], litI 1] ]
              , appVN 'toInteger [lo] ]
        
        , if signed
          then
            funXY 'quotRem $
              CondE (appVN 'testMsb [x])
                (CondE (appVN 'testMsb [y])
                   (LetE [vals [q, r] $
                            appV 'quotRem
                              [ appV 'unsignedWord [appVN 'negate [x]]
                              , appV 'unsignedWord [appVN 'negate [y]] ]]
                      (TupE [ appVN 'signedWord [q]
                            , appV 'signedWord [appVN 'negate [r]] ]))
                   (LetE [vals [q, r] $
                            appV 'quotRem
                              [ appV 'unsignedWord [appVN 'negate [x]]
                              , appVN 'unsignedWord [y] ]]
                      (TupE [ appV 'signedWord [appVN 'negate [q]]
                            , appV 'signedWord [appVN 'negate [r]] ])))
                (CondE (appVN 'testMsb [y])
                   (LetE [vals [q, r] $
                            appV 'quotRem
                              [ appVN 'unsignedWord [x]
                              , appV 'unsignedWord [appVN 'negate [y]] ]]
                      (TupE [ appV 'signedWord [appVN 'negate [q]]
                            , appVN 'signedWord [r] ]))
                   (LetE [vals [q, r] $
                            appV 'quotRem
                              [ appVN 'unsignedWord [x]
                              , appVN 'unsignedWord [y] ]]
                      (TupE [ appVN 'signedWord [q]
                            , appVN 'signedWord [r] ])))
          else
            funHiLo2XY' 'quotRem
              (CondE (appV '(&&) [ appVN '(==) [hi', 'allZeroes]
                                 , appVN '(==) [lo', 'allZeroes] ])
                 (appV 'error [litS "divide by zero"])
                 (CaseE (appVN 'compare [hi, hi'])
                    [ match (ConP 'LT []) (TupE [zeroE, VarE x])
                    , match (ConP 'EQ [])
                        (CaseE (appVN 'compare [lo, lo'])
                           [ match (ConP 'LT []) (TupE [zeroE, VarE x])
                           , match (ConP 'EQ []) (TupE [oneE, zeroE])
                           , Match (ConP 'GT [])
                               (GuardedB $ return
                                  ( NormalG (appVN '(==) [hi', 'allZeroes])
                                  , TupE [ appWN ['allZeroes, t2]
                                         , appWN ['allZeroes, t1] ]))
                               [vals [t2, t1] $ appVN 'quotRem [lo, lo']]
                           , match (ConP 'GT []) $
                               TupE [ oneE
                                    , appW [zeroE, appVN '() [lo, lo']] ]
                           ])
                    , Match (ConP 'GT [])
                        (GuardedB $ return
                           ( NormalG (appVN '(==) [lo', 'allZeroes])
                           , TupE
                               [ appW [zeroE, appVN 'fromIntegral [t2]]
                               , appW [appVN 'fromIntegral [t1], VarE lo]
                               ] ))
                        [vals [t2, t1] $ appVN 'quotRem [hi, hi']]
                    , Match (ConP 'GT [])
                        (GuardedB $ return
                           ( NormalG (appV '(&&)
                                        [ appVN '(==) [hi', 'allZeroes]
                                        , appVN '(==) [lo', 'maxBound] ])
                           , CondE (appVN '(==) [t2, 'allZeroes])
                               (CondE (appVN '(==) [t1, 'maxBound])
                                  (TupE
                                     [ appV '(+)
                                         [ appWN ['allZeroes, z] 
                                         , oneE ]
                                     , zeroE ])
                                  (TupE
                                     [ appWN ['allZeroes, z]
                                     , appWN ['allZeroes, t1] ]))
                               (CondE (appVN '(==) [t1, 'maxBound])
                                  (TupE
                                     [ appV '(+)
                                         [appWN ['allZeroes, z], litI 2]
                                     , oneE ])
                                  (CondE
                                     (appV '(==)
                                        [ VarE t1
                                        , appVN 'xor ['maxBound, 'lsb]
                                        ])
                                     (TupE
                                        [ appV '(+)
                                            [appWN ['allZeroes, z], litI 2]
                                        , zeroE ])
                                     (TupE
                                        [ appV '(+)
                                            [appWN ['allZeroes, z], oneE]
                                        , appW [ zeroE
                                               , appVN '(+) [t1, 'lsb] ]
                                        ])))
                           ))
                        [ val z $ appVN 'fromIntegral [hi]
                        , vals [t2, t1] $ appVN 'unwrappedAdd [z, lo] ]
                    , Match (ConP 'GT [])
                        (GuardedB $ return
                           ( NormalG (appVN '(==) [hi', 'allZeroes])
                           , TupE [VarE t2, appWN ['allZeroes, t1]] ))
                        [vals [t2, t1] $ appVN div1 [hi, lo, lo']]
                    , match' (ConP 'GT [])
                        (CondE (appVN '(==) [t1, t2])
                               (TupE [oneE, appVN '() [x, y]])
                               (TupE [ appW [zeroE, appVN 'fromIntegral [q2]]
                                     , appVN 'shiftR [r2, t2] ]))
                        [ val t1 $ appVN 'leadingZeroes [hi]
                        , val t2 $ appVN 'leadingZeroes [hi']
                        , val z $ appV 'shiftR
                                    [ VarE hi
                                    , appV '()
                                        [ appV 'bitSize
                                            [SigE (VarE 'undefined) hiT]
                                        , VarE t2 ]
                                    ]
                        , ValD (ConP cn [VarP hhh, VarP hll])
                            (NormalB $ appVN 'shiftL [x, t2]) [] 
                        , ValD (AsP v $ ConP cn [VarP lhh, VarP lll])
                            (NormalB $ appVN 'shiftL [y, t2]) []
                        , ValD (TupP [ TupP [LitP (IntegerL 0), VarP q1]
                                     , VarP r1 ])
                            (NormalB $ appVN div2 [z, hhh, lhh]) []
                        , vals [t4, t3] $
                            appV 'unwrappedMul
                              [appVN 'fromIntegral [q1], VarE lll]
                        , val t5 $ appW [appVN 'fromIntegral [t4], VarE t3]
                        , val t6 $ appWN [r1, hll]
                        , vals [t8, t7] $ appVN 'unwrappedAdd [t6, v]
                        , vals [t10, t9] $ appVN 'unwrappedAdd [t7, v]
                        , vals [q2, r2] $
                            CondE (appVN '(>) [t5, t6])
                              (CondE (appV '(==) [appVN 'loWord [t8], zeroE])
                                 (CondE (appVN '(>=) [t7, t5])
                                    (TupE [ appVN '() [q1, 'lsb]
                                          , appVN '() [t7, t5] ])
                                    (CondE (appV '(==) [ appVN 'loWord [t10]
                                                       , zeroE ])
                                       (TupE [ appV '() [VarE q1, litI 2]
                                             , appVN '() [t9, t5] ])
                                       (TupE [ appV '() [VarE q1, litI 2]
                                             , appV '(+)
                                                 [ appVN '() ['maxBound, t5]
                                                 , appVN '(+) [t9, 'lsb]
                                                 ]
                                             ])))
                                 (TupE [ appVN '() [q1, 'lsb]
                                       , appV '(+)
                                           [ appVN '() ['maxBound, t5]
                                           , appVN '(+) [t7, 'lsb] ]
                                       ]))
                              (TupE [VarE q1, appVN '() [t6, t5]])
                        ]
                    ]))
              [ FunD div1 $ return $
                  Clause [VarP hhh, VarP hll, VarP by]
                    (NormalB (appVN go [hhh, hll, 'allZeroes]))
                    [ vals [t2, t1] $ appVN 'quotRem ['maxBound, by]
                    , FunD go $ return $
                        Clause [VarP h, VarP l, VarP c]
                          (NormalB
                             (CondE (appVN '(==) [z, 'allZeroes])
                                (TupE [ appV '(+)
                                          [ VarE c
                                          , appV '(+)
                                              [ appW [ appVN 'fromIntegral [t8]
                                                     , VarE t7 ]
                                              , appWN ['allZeroes, t10] ]
                                          ]
                                      , VarE t9 ])
                                (appV go
                                   [ appVN 'fromIntegral [z]
                                   , VarE t5
                                   , appV '(+)
                                       [ VarE c
                                       , appW [ appVN 'fromIntegral [t8]
                                              , VarE t7 ]
                                       ]
                                   ])))
                          [ val h1 $ appVN 'fromIntegral [h]
                          , vals [t4, t3] $
                              appV 'unwrappedMul
                                [VarE h1, appVN '(+) [t1, 'lsb]]
                          , vals [t6, t5] $ appVN 'unwrappedAdd [t3, l]
                          , val z $ appVN '(+) [t4, t6]
                          , vals [t8, t7] $ appVN 'unwrappedMul [h1, t2]
                          , vals [t10, t9] $ appVN 'quotRem [t5, by] ]
                    ]
              , FunD div2 $ return $
                  Clause [VarP hhh, VarP hll, VarP by]
                    (NormalB (appV go [ VarE hhh
                                      , VarE hll
                                      , TupE [zeroE, zeroE]]))
                    [ vals [t2, t1] $ appVN 'quotRem ['maxBound, by]
                    , FunD go $ return $
                        Clause [VarP h, VarP l, VarP c]
                          (NormalB
                             (CondE (appVN '(==) [z, 'allZeroes])
                                (TupE [ appV addT
                                          [ VarE c
                                          , appV addT
                                              [ TupE [VarE t8 , VarE t7]
                                              , TupE [zeroE, VarE t10] ]
                                          ]
                                      , VarE t9 ])
                                (appV go
                                   [ VarE z
                                   , VarE t5
                                   , appV addT
                                       [ VarE c
                                       , TupE [VarE t8, VarE t7]
                                       ]
                                   ])))
                          [ vals [t4, t3] $
                              appV 'unwrappedMul
                                [VarE h, appVN '(+) [t1, 'lsb]]
                          , vals [t6, t5] $ appVN 'unwrappedAdd [t3, l]
                          , val z $ appVN '(+) [t4, t6]
                          , vals [t8, t7] $ appVN 'unwrappedMul [h, t2]
                          , vals [t10, t9] $ appVN 'quotRem [t5, by] ]
                    , FunD addT $ return $
                        Clause [ TupP [VarP lhh, VarP lhl]
                               , TupP [VarP llh, VarP lll]
                               ]
                          (NormalB (TupE [ appV '(+)
                                             [ VarE t4
                                             , appVN '(+) [lhh, llh]
                                             ]
                                         , VarE t3
                                         ]))
                          [vals [t4, t3] $ appVN 'unwrappedAdd [lhl, lll]]
                    ]
              ]
        
        , if signed
          then
            funXY 'divMod $
              CondE (appVN 'testMsb [x])
                (CondE (appVN 'testMsb [y])
                   (LetE [vals [q, r] $
                            appV 'quotRem
                              [ appV 'unsignedWord [appVN 'negate [x]]
                              , appV 'unsignedWord [appVN 'negate [y]] ]]
                      (TupE [ appVN 'signedWord [q]
                            , appV 'signedWord [appVN 'negate [r]] ]))
                   (LetE [ vals [q, r] $
                             appV 'quotRem
                               [ appV 'unsignedWord [appVN 'negate [x]]
                               , appVN 'unsignedWord [y] ]
                         , val q1 $ appV 'signedWord [appVN 'negate [q]]
                         , val r1 $ appV 'signedWord [appVN 'negate [r]]
                         ]
                      (CondE (appVN '(==) [r, 'allZeroes])
                         (TupE [VarE q1, VarE r1])
                         (TupE [ appVN '() [q1, 'lsb]
                               , appVN '(+) [r1, y] ]))))
                (CondE (appVN 'testMsb [y])
                   (LetE [ vals [q, r] $
                             appV 'quotRem
                               [ appVN 'unsignedWord [x]
                               , appV 'unsignedWord [appVN 'negate [y]] ]
                         , val q1 $ appV 'signedWord [appVN 'negate [q]]
                         , val r1 $ appVN 'signedWord [r]
                         ]
                      (CondE (appVN '(==) [r, 'allZeroes])
                         (TupE [VarE q1, VarE r1])
                         (TupE [ appVN '() [q1, 'lsb]
                               , appVN '(+) [r1, y] ])))
                   (LetE [vals [q, r] $
                            appV 'quotRem
                              [ appVN 'unsignedWord [x]
                              , appVN 'unsignedWord [y] ]]
                      (TupE [ appVN 'signedWord [q]
                            , appVN 'signedWord [r] ])))
          else
            fun 'divMod $ VarE 'quotRem
        ] ++
        if signed then [] else [inline 'divMod]
    , inst ''Show [tp]
        [ fun 'show $ appVN '(.) ['show, 'toInteger]
        , inline 'show ]
    , inst ''Read [tp]
        
        [ funXY 'readsPrec $
            appV 'fmap [ LamE [TupP [VarP q, VarP r]]
                              (TupE [appVN 'fromInteger [q], VarE r])
                       , appVN 'readsPrec [x, y] ]
        ]
    , inst ''Hashable [tp]
#if MIN_VERSION_hashable(1,2,0)
        
        [ funXHiLo 'hashWithSalt $
            appV 'hashWithSalt [appVN 'hashWithSalt [x, hi], VarE lo]
#else
        
        [ funHiLo 'hash $ appV 'combine [appVN 'hash [hi], appVN 'hash [lo]]
        , inline 'hash
#endif
        , inline 'hashWithSalt ]
    , inst ''Ix [tp]
        
        [ funTup 'range $ appVN 'enumFromTo [x, y]
        , inline 'range
        
        , funTupLZ 'unsafeIndex $
            appV '() [appVN 'fromIntegral [z], appVN 'fromIntegral [x]]
        , inline 'unsafeIndex
        
        , funTupZ 'inRange $
            appV '(&&) [appVN '(>=) [z, x], appVN '(<=) [z, y]]
        , inline 'inRange ]
    , inst ''Bits [tp] $
        
        [ fun_ 'bitSize $
            appV '(+)
              [ appV 'bitSize [SigE (VarE 'undefined) hiT]
              , appV 'bitSize [SigE (VarE 'undefined) loT] ]
        , inline 'bitSize
        
        , fun_ 'isSigned $ ConE $ if signed then 'True else 'False
        , inline 'isSigned
        
        , funHiLo 'complement $
            appW [appVN 'complement [hi], appVN 'complement [lo]]
        , inline 'complement
        
        , funHiLo2 'xor $ appW [appVN 'xor [hi, hi'], appVN 'xor [lo, lo']]
        , inline 'xor
        
        , funHiLo2 '(.&.) $
            appW [appVN '(.&.) [hi, hi'], appVN '(.&.) [lo, lo']]
        , inline '(.&.)
        
        , funHiLo2 '(.|.) $
            appW [appVN '(.|.) [hi, hi'], appVN '(.|.) [lo, lo']]
        , inline '(.|.)
        
        , funHiLoX' 'shiftL
            (CondE (appV '(>) [VarE y, litI 0])
                   (appW
                      [ appV '(.|.)
                          [ appVN 'shiftL [hi, x]
                          , appV 'fromIntegral [appVN 'shiftR [lo, y]] ]
                      , appVN 'shiftL [lo, x] ])
                   (appW [ appV 'fromIntegral
                             [appV 'shiftL [VarE lo, appVN 'negate [y]]]
                         , zeroE ]))
            [val y $
               appV '() [ appV 'bitSize [SigE (VarE 'undefined) loT]
                         , VarE x ]]
        
        , funHiLoX' 'shiftR
            (appW [ appVN 'shiftR [hi, x]
                  , CondE (appV '(>=) [VarE y, litI 0])
                          (appV '(.|.)
                             [ appV 'shiftL
                                 [appVN 'fromIntegral [hi], VarE y]
                             , appVN 'shiftR [lo, x] ])
                          (VarE z) ])
            [ val y $ appV '() [ appV 'bitSize [SigE (VarE 'undefined) loT]
                                , VarE x ]
            , val z $
                if signed
                then appV 'fromIntegral
                       [appV 'shiftR
                          [ SigE (appVN 'fromIntegral [hi])
                                 (AppT (ConT ''SignedWord) loT)
                          , appVN 'negate [y] ]]
                else appV 'shiftR [ appVN 'fromIntegral [hi]
                                  , appVN 'negate [y] ]
            ]
        
        , if signed
          then
            funXY 'rotateL $
              appV 'signedWord
                   [appV 'rotateL [appVN 'unsignedWord [x], VarE y]]
          else 
            funHiLoX' 'rotateL
              (CondE (appV '(>=) [VarE y, litI 0])
                 (appW
                    [ appV '(.|.)
                        [ appV 'fromIntegral [appVN 'shiftL [lo, y]]
                        , appVN 'shiftR [hi, z] ]
                    , appV '(.|.)
                        [ appV 'shiftL
                            [ appVN 'fromIntegral [hi]
                            , appV '()
                                [ appV 'bitSize [SigE (VarE 'undefined) loT]
                                , VarE z ]
                            ]
                        , appVN 'shiftR [lo, z] ]
                    ])
                 (appW
                    [ appV '(.|.)
                        [ appV 'fromIntegral
                            [appV 'shiftR [VarE lo, appVN 'negate [y]]]
                        , appVN 'shiftL [hi, x] ]
                    , appV '(.|.)
                        [ appV 'shift
                            [ appVN 'fromIntegral [hi]
                            , appV '()
                                [ appV 'bitSize [SigE (VarE 'undefined) loT]
                                , VarE z] ]
                        , appV '(.|.)
                            [appVN 'shiftL [lo, x], appVN 'shiftR [lo, z]] ]
                    ]))
              [ val y $
                  appV '() [ VarE x
                            , appV 'bitSize [SigE (VarE 'undefined) loT] ]
              , val z $
                  appV '()
                    [ appV 'bitSize [SigE (VarE 'undefined) tpT]
                    , VarE x ]
              ]
        
        , funXY 'rotateR $
            appV 'rotateL
              [ VarE x
              , appV '()
                  [appV 'bitSize [SigE (VarE 'undefined) tpT], VarE y]
              ]
        , inline 'rotateR
        
        , funX' 'bit (CondE (appV '(>=) [VarE y, litI 0])
                            (appW [appVN 'bit [y], zeroE])
                            (appW [zeroE, appVN 'bit [x]]))
            [val y $
               appV '() [ VarE x
                         , appV 'bitSize [SigE (VarE 'undefined) loT] ]]
        , inlinable 'bit
        
        , funHiLoX' 'setBit
            (CondE (appV '(>=) [VarE y, litI 0])
                   (appW [appVN 'setBit [hi, y], VarE lo])
                   (appW [VarE hi, appVN 'setBit [lo, x]]))
            [val y $
               appV '() [ VarE x
                         , appV 'bitSize [SigE (VarE 'undefined) loT] ]]
        , inlinable 'setBit
        
        , funHiLoX' 'clearBit
            (CondE (appV '(>=) [VarE y, litI 0])
                   (appW [appVN 'clearBit [hi, y], VarE lo])
                   (appW [VarE hi, appVN 'clearBit [lo, x]]))
            [val y $
               appV '() [ VarE x
                         , appV 'bitSize [SigE (VarE 'undefined) loT] ]]
        , inlinable 'clearBit
        
        , funHiLoX' 'complementBit
            (CondE (appV '(>=) [VarE y, litI 0])
                   (appW [appVN 'complementBit [hi, y], VarE lo])
                   (appW [VarE hi, appVN 'complementBit [lo, x]]))
            [val y $
               appV '() [ VarE x
                         , appV 'bitSize [SigE (VarE 'undefined) loT] ]]
        , inlinable 'complementBit
        
        , funHiLoX' 'testBit
            (CondE (appV '(>=) [VarE y, litI 0])
                   (appVN 'testBit [hi, y])
                   (appVN 'testBit [lo, x]))
            [val y $
               appV '() [ VarE x
                         , appV 'bitSize [SigE (VarE 'undefined) loT] ]]
        , inlinable 'testBit
        
        , funHiLo 'popCount
            (appV '(+) [appVN 'popCount [hi], appVN 'popCount [lo]])
        , inline 'popCount
        ] ++
        if signed then [inline 'rotateL] else []
    , inst ''BinaryWord [tp]
        [ TySynInstD ''UnsignedWord [tpT] $
            ConT $ if signed then otp else tp
        , TySynInstD ''SignedWord [tpT] $
            ConT $ if signed then tp else otp
        
        , if signed
          then
            funHiLo 'unsignedWord $
              appC ocn [appVN 'unsignedWord [hi], VarE lo]
          else
            fun 'unsignedWord $ VarE 'id
        , inline 'unsignedWord
        
        , if signed
          then
            fun 'signedWord $ VarE 'id
          else
            funHiLo 'signedWord $
              appC ocn [appVN 'signedWord [hi], VarE lo]
        , inline 'signedWord
        
        , if signed
          then
            funXY' 'unwrappedAdd (TupE [VarE z, VarE t4])
              [ val t1 $ CondE (appVN 'testMsb [x])
                               (VarE 'maxBound) (VarE 'minBound)
              , val t2 $ CondE (appVN 'testMsb [y])
                               (VarE 'maxBound) (VarE 'minBound)
              , vals [t3, t4] $
                  appV 'unwrappedAdd [ appVN 'unsignedWord [x]
                                     , appVN 'unsignedWord [y] ]
              , val z $
                  appV 'signedWord [appV '(+) [VarE t1, appVN '(+) [t2, t3]]]
              ]
          else
            funHiLo2' 'unwrappedAdd
              (TupE [appWN ['allZeroes, z], appWN [y, x]])
              [ vals [t1, x] $ appVN 'unwrappedAdd [lo, lo']
              , vals [t3, t2] $
                  appV 'unwrappedAdd [VarE hi, appVN 'fromIntegral [t1]]
              , vals [t4, y] $ appVN 'unwrappedAdd [t2, hi']
              , val z $ appV 'fromIntegral [appVN '(+) [t3, t4]]
              ]
        
        , if signed
          then
            funHiLo2' 'unwrappedMul (TupE [VarE x, VarE y])
              [ val t1 $
                  appV '(+) [ appW [ appVN 'complement [hi']
                                   , appVN 'complement [lo'] ]
                            , oneE ]
              , val t2 $
                  appV '(+) [ appW [ appVN 'complement [hi]
                                   , appVN 'complement [lo] ]
                            , oneE ]
              , vals [t3, y] $
                  appV 'unwrappedMul
                    [ appC ocn [appVN 'unsignedWord [hi], VarE lo]
                    , appC ocn [appVN 'unsignedWord [hi'], VarE lo'] ]
              , val z $ appVN 'signedWord [t3]
              , val x $
                  CondE (appVN 'testMsb [hi])
                    (CondE (appVN 'testMsb [hi'])
                       (appV '(+) [VarE z, appVN '(+) [t1, t2]])
                       (appVN '(+) [z, t1]))
                    (CondE (appVN 'testMsb [hi'])
                       (appVN '(+) [z, t2]) (VarE z))
              ]
          else
            funHiLo2' 'unwrappedMul
              (TupE [ appW
                        [ appV '(+)
                            [ VarE hhh
                            , appV '(+)
                                [ appV 'fromIntegral [appVN 'shiftR [t9, y]]
                                , appVN 'shiftL [x, z] ]
                            ]
                        , appV '(.|.) [ appVN 'shiftL [t9, z]
                                      , appVN 'shiftR [t3, y] ]
                        ]
                    , appW [appVN 'fromIntegral [t3], VarE lll]
                    ])
              [ vals [llh, lll] $ appVN 'unwrappedMul [lo, lo']
              , vals [hlh, hll] $
                  appV 'unwrappedMul [appVN 'fromIntegral [hi], VarE lo']
              , vals [lhh, lhl] $
                  appV 'unwrappedMul [VarE lo, appVN 'fromIntegral [hi']]
              , vals [hhh, hhl] $ appVN 'unwrappedMul [hi, hi']
              , vals [t2, t1] $ appVN 'unwrappedAdd [llh, hll]
              , vals [t4, t3] $ appVN 'unwrappedAdd [t1, lhl]
              , vals [t6, t5] $
                  appV 'unwrappedAdd [ appVN 'fromIntegral [hhl]
                                     , appVN '(+) [t2, t4] ]
              , vals [t8, t7] $ appVN 'unwrappedAdd [t5, lhh]
              , vals [t10, t9] $ appVN 'unwrappedAdd [t7, hlh]
              , val x $
                  appV 'fromIntegral
                    [appV '(+) [VarE t6, appVN '(+) [t8, t10]]]
              , val y $ appV 'bitSize [SigE (VarE 'undefined) hiT]
              , val z $ appV '() [ appV 'bitSize [SigE (VarE 'undefined) loT]
                                  , VarE y ]
              ]
        
        , if signed
          then
            fun 'leadingZeroes $ appVN '(.) ['leadingZeroes, 'unsignedWord]
          else
            funHiLo' 'leadingZeroes
              (CondE (appVN '(==) [x, y])
                     (appV '(+) [VarE y, appVN 'leadingZeroes [lo]])
                     (VarE x))
              [ val x $ appVN 'leadingZeroes [hi]
              , val y $ appV 'bitSize [SigE (VarE 'undefined) hiT]
              ]
        , if signed then inlinable 'leadingZeroes
                    else inline 'leadingZeroes
        
        , if signed
          then
            fun 'trailingZeroes $ appVN '(.) ['trailingZeroes, 'unsignedWord]
          else
            funHiLo' 'trailingZeroes
              (CondE (appVN '(==) [x, y])
                     (appV '(+) [VarE y, appVN 'trailingZeroes [hi]])
                     (VarE x))
              [ val x $ appVN 'trailingZeroes [lo]
              , val y $ appV 'bitSize [SigE (VarE 'undefined) loT]
              ]
        , if signed then inlinable 'trailingZeroes
                    else inline 'trailingZeroes
        
        , fun 'allZeroes $ appWN ['allZeroes, 'allZeroes]
        , inline 'allZeroes
        
        , fun 'allOnes $ appWN ['allOnes, 'allOnes]
        , inline 'allOnes
        
        , fun 'msb $ appWN ['msb, 'allZeroes]
        , inline 'msb
        
        , fun 'lsb $ appWN ['allZeroes, 'lsb]
        , inline 'lsb
        
        , funHi 'testMsb $ appVN 'testMsb [hi]
        , inline 'testMsb
        
        , funLo 'testLsb $ appVN 'testLsb [lo]
        , inline 'testLsb
        ]
    ]
  where
    x    = mkName "x"
    y    = mkName "y"
    z    = mkName "z"
    t1   = mkName "t1"
    t2   = mkName "t2"
    t3   = mkName "t3"
    t4   = mkName "t4"
    t5   = mkName "t5"
    t6   = mkName "t6"
    t7   = mkName "t7"
    t8   = mkName "t8"
    t9   = mkName "t9"
    t10  = mkName "t10"
    v    = mkName "v"
    q    = mkName "q"
    q1   = mkName "q1"
    q2   = mkName "q2"
    r    = mkName "r"
    r1   = mkName "r1"
    r2   = mkName "r2"
    lll  = mkName "lll"
    llh  = mkName "llh"
    lhl  = mkName "lhl"
    lhh  = mkName "lhh"
    hll  = mkName "hll"
    hlh  = mkName "hlh"
    hhl  = mkName "hhl"
    hhh  = mkName "hhh"
    h    = mkName "h"
    h1   = mkName "h1"
    l    = mkName "l"
    div1 = mkName "div1"
    div2 = mkName "div2"
    addT = mkName "addT"
    by   = mkName "by_"
    go   = mkName "go_"
    c    = mkName "c"
    next = mkName "next_"
    step = mkName "step_"
    to   = mkName "to_"
    down = mkName "down_"
    up   = mkName "up_"
    hi   = mkName "hi_"
    lo   = mkName "lo_"
    hi'  = mkName "hi'"
    lo'  = mkName "lo'"
    tpT  = ConT tp
    inst cls params = InstanceD [] (foldl AppT (ConT cls) (ConT <$> params))
    fun n e       = FunD n [Clause [] (NormalB e) []]
    fun_ n e      = FunD n [Clause [WildP] (NormalB e) []]
    funX' n e ds  = FunD n [Clause [VarP x] (NormalB e) ds]
    funX n e      = funX' n e []
    funXY' n e ds = FunD n [Clause [VarP x, VarP y] (NormalB e) ds]
    funXY n e     = funXY' n e []
    funTup n e    = FunD n [Clause [TupP [VarP x, VarP y]] (NormalB e) []]
    funTupZ n e   =
      FunD n [Clause [TupP [VarP x, VarP y], VarP z] (NormalB e) []]
    funTupLZ n e  =
      FunD n [Clause [TupP [VarP x, WildP], VarP z] (NormalB e) []]
    funLo n e     = FunD n [Clause [ConP cn [WildP, VarP lo]] (NormalB e) []]
    funHi n e     = FunD n [Clause [ConP cn [VarP hi, WildP]] (NormalB e) []]
    funHiLo n e   = funHiLo' n e []
    funHiLo' n e ds  =
      FunD n [Clause [ConP cn [VarP hi, VarP lo]] (NormalB e) ds]
    funHiLoX' n e ds =
      FunD n [Clause [ConP cn [VarP hi, VarP lo], VarP x] (NormalB e) ds]
    funHiLo2 n e     = funHiLo2' n e []
    funHiLo2' n e ds =
      FunD n [Clause [ ConP cn [VarP hi, VarP lo]
                     , ConP cn [VarP hi', VarP lo'] ]
                     (NormalB e) ds]
    funHiLo2XY' n e ds =
      FunD n [Clause [ AsP x (ConP cn [VarP hi, VarP lo])
                     , AsP y (ConP cn [VarP hi', VarP lo']) ]
                     (NormalB e) ds]
    funXHiLo n e  = FunD n [Clause [VarP x, ConP cn [VarP hi, VarP lo]]
                                   (NormalB e) []]
    match' p e ds = Match p (NormalB e) ds
    match p e     = match' p e []
    inline n = PragmaD $ InlineP n Inline FunLike AllPhases
    inlinable n = PragmaD $ InlineP n Inlinable FunLike AllPhases
    val n e   = ValD (VarP n) (NormalB e) []
    vals ns e = ValD (TupP (VarP <$> ns)) (NormalB e) []
    app f   = foldl AppE f
    appN f  = app f . fmap VarE
    appV f  = app (VarE f)
    appC f  = app (ConE f)
    appW    = appC cn
    appVN f = appN (VarE f)
    appCN f = appN (ConE f)
    appWN   = appCN cn
    litI = LitE . IntegerL
    litS = LitE . StringL
    zeroE = VarE 'allZeroes
    oneE  = VarE 'lsb
    mkRules = do
      let idRule = RuleP ("fromIntegral/" ++ show tp ++ "->" ++ show tp) []
                         (VarE 'fromIntegral)
                         (SigE (VarE 'id) (AppT (AppT ArrowT tpT) tpT))
                         AllPhases
          signRule = RuleP ("fromIntegral/" ++ show tp ++ "->" ++ show otp) []
                           (VarE 'fromIntegral)
                           (SigE (VarE (if signed then 'unsignedWord
                                                  else 'signedWord))
                                 (AppT (AppT ArrowT tpT) (ConT otp)))
                           AllPhases
      mkRules' [idRule, signRule] loT
               (VarE 'loWord)
               (VarE 'extendLo)
               (VarE 'signExtendLo)
    mkRules' rules t narrowE extE signExtE = do
      let narrowRule = RuleP ("fromIntegral/" ++ show tp ++ "->" ++ showT t)
                             []
                             (VarE 'fromIntegral)
                             (SigE narrowE (AppT (AppT ArrowT tpT) t))
                             AllPhases
          extRule = RuleP ("fromIntegral/" ++ showT t ++ "->" ++ show tp)
                          []
                          (VarE 'fromIntegral)
                          (SigE extE (AppT (AppT ArrowT t) tpT))
                          AllPhases
      signedRules ← do
        insts ← reifyInstances ''SignedWord [t]
        case insts of
          [TySynInstD _ _ signT] → return $
            [ RuleP ("fromIntegral/" ++ show tp ++ "->" ++ showT signT)
                    []
                    (VarE 'fromIntegral)
                    (SigE (AppE (appVN '(.) ['signedWord]) narrowE)
                          (AppT (AppT ArrowT tpT) signT))
                    AllPhases
            , RuleP ("fromIntegral/" ++ showT signT ++ "->" ++ show tp)
                    []
                    (VarE 'fromIntegral)
                    (SigE signExtE (AppT (AppT ArrowT signT) tpT))
                    AllPhases ]
          _ → return []
      let rules' = narrowRule : extRule : signedRules ++ rules
      case smallerStdTypes t of
        Just ts → do
          let smallRules = ts >>= \(uSmallName, sSmallName) →
                let uSmallT = ConT uSmallName
                    sSmallT = ConT sSmallName in
                [ RuleP ("fromIntegral/" ++
                         show tp ++ "->" ++ show uSmallName)
                        []
                        (VarE 'fromIntegral)
                        (SigE (appV '(.) [VarE 'fromIntegral, narrowE])
                              (AppT (AppT ArrowT tpT) uSmallT))
                        AllPhases
                , RuleP ("fromIntegral/" ++
                         show uSmallName ++ "->" ++ show tp)
                        []
                        (VarE 'fromIntegral)
                        (SigE (appV '(.) [extE, VarE 'fromIntegral])
                              (AppT (AppT ArrowT uSmallT) tpT))
                        AllPhases
                , RuleP ("fromIntegral/" ++
                         show tp ++ "->" ++ show sSmallName)
                        []
                        (VarE 'fromIntegral)
                        (SigE (appV '(.) [VarE 'fromIntegral, narrowE])
                              (AppT (AppT ArrowT tpT) sSmallT))
                        AllPhases
                , RuleP ("fromIntegral/" ++
                         show sSmallName ++ "->" ++ show tp)
                        []
                        (VarE 'fromIntegral)
                        (SigE (appV '(.) [signExtE, VarE 'fromIntegral])
                              (AppT (AppT ArrowT sSmallT) tpT))
                        AllPhases
                ]
          return $ PragmaD <$> rules' ++ smallRules
        _ → do
          insts ← reifyInstances ''LoWord [t]
          case insts of
            [TySynInstD _ _ t'] →
              mkRules' rules' t'
                       (appV '(.) [VarE 'loWord, narrowE])
                       (appV '(.) [VarE 'extendLo, extE])
                       (appV '(.) [VarE 'signExtendLo, signExtE])
            _ → return $ PragmaD <$> rules'
    showT (ConT n) = show n
    showT t = show t
    stdTypes = [(''Word64, ''Int64), (''Word32, ''Int32),
                (''Word16, ''Int16), (''Word8, ''Int8)]
    smallerStdTypes t = smallerStdTypes' t stdTypes
    smallerStdTypes' _ [] = Nothing
    smallerStdTypes' t ((ut, _) : ts)
      | ConT ut == t = Just ts
      | otherwise    = smallerStdTypes' t ts