module Data.HSet.Union where

import Data.HSet.Remove
import Data.HSet.Type
import Data.Typeable
import TypeFun.Data.List
import TypeFun.Data.Peano

#if !(MIN_VERSION_base(4, 8, 0))
import Control.Applicative
#endif

type family MayFstIndexSnd (ts1 :: [k]) (ts2 :: [k]) :: (Maybe N) where
  MayFstIndexSnd (e ': els) x = IndexOfMay e x
  MayFstIndexSnd '[]        x = 'Nothing

class ( fidx ~ (MayFstIndexSnd els1 els2)
      , sidx ~ (MayFstIndexSnd els2 els1) )
      => HUnion els1 els2 elsr fidx sidx
       | els1 els2 fidx sidx -> elsr where
  hunion :: HSet els1 -> HSet els2 -> HSet elsr

instance HUnion '[] '[] '[] 'Nothing 'Nothing where
  hunion _ _ = HSNil

instance HUnion '[] (e ': els) (e ': els) 'Nothing 'Nothing where
  hunion _ a = a

instance HUnion (e ': els) '[] (e ': els) 'Nothing 'Nothing where
  hunion a _ = a

instance ( HUnionable els1 els2 elsr
         , NotElem e1 (e2 ': elsr)
         , NotElem e2 elsr
         , 'Nothing ~ (MayFstIndexSnd (e1 ': els1) (e2 ': els2))
         , 'Nothing ~ (MayFstIndexSnd (e2 ': els2) (e1 ': els1)) )
         => HUnion (e1 ': els1) (e2 ': els2) (e1 ': e2 ': elsr) 'Nothing 'Nothing where
  hunion (HSCons e1 els1) (HSCons e2 els2) = HSCons e1 $ HSCons e2 $ hunion els1 els2

instance ( HUnionable els1 els2 elsr
         , NotElem e elsr )
         => HUnion (e ': els1) (e ': els2) (e ': elsr) ('Just 'Z) ('Just 'Z) where
  hunion (HSCons e els1) (HSCons _ els2) = HSCons e $ hunion els1 els2

instance ( HRemove els2 elsx fi
         , HUnionable els1 elsx elsr
         , NotElem e1 elsr
         , ('Just ('S fi)) ~ (MayFstIndexSnd (e1 ': els1) (e2 ': els2))
         , ('Just si) ~ (MayFstIndexSnd (e2 ': els2) (e1 ': els1)) )
         => HUnion (e1 ': els1) (e2 ': els2) (e1 ': elsr) ('Just ('S fi)) ('Just si) where
  hunion (HSCons e1 els1) (HSCons _ els2) = HSCons e1 $ hunion els1 $ hremove (Proxy :: Proxy fi) els2

instance ( HRemove els2 elsx fi
         , HUnionable els1 elsx elsr
         , NotElem e1 elsr
         , ('Just fi) ~ (MayFstIndexSnd (e1 ': els1) els2)
         , 'Nothing ~ (MayFstIndexSnd els2 (e1 ': els1)) )
         => HUnion (e1 ': els1) els2 (e1 ': elsr) ('Just fi) 'Nothing where
  hunion (HSCons e1 els1) els2 = HSCons e1 $ hunion els1 $ hremove (Proxy :: Proxy fi) els2

instance ( HUnionable els1 els2 elsr
         , NotElem e1 elsr
         , 'Nothing ~ (MayFstIndexSnd (e1 ': els1) (e2 ': els2))
         , ('Just si) ~ (MayFstIndexSnd (e2 ': els2) (e1 ': els1)) )
         => HUnion (e1 ': els1) (e2 ': els2) (e1 ': elsr) 'Nothing ('Just si) where
  hunion (HSCons e1 els1) (HSCons _ els2) = HSCons e1 $ hunion els1 els2

type HUnionable els1 els2 elsr =
  HUnion els1 els2 elsr (MayFstIndexSnd els1 els2) (MayFstIndexSnd els2 els1)