{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}

-- Note: this file may contain spoilers
-- (although I would be really surprised if it did, I haven't seen the films)
module StarWars where

import GHC.Generics
import Data.Generics.Product

data Episode = NEWHOPE | EMPIRE | JEDI
  deriving (Generic, Show, Eq)

data Character = Character
   { name      :: String
   , friends   :: [Character]
   , appearsIn :: [Episode]
   } deriving (Generic, Show, Eq)

data Human = Human
  { name        :: String
  , friends     :: [Character]
  , appearsIn   :: [Episode]
  , homePlanet  :: String
  } deriving (Generic, Show)

data Droid = Droid
  { friends         :: [Character]
  , appearsIn       :: [Episode]
  , name            :: String
  , primaryFunction :: String
  } deriving (Generic, Show)

luke :: Human
luke = Human
  { name           = "Luke Skywalker"
  , friends        = []
  , appearsIn      = [NEWHOPE, EMPIRE, JEDI]
  , homePlanet     = "Saturn (?)"
  }

r2d2 :: Droid
r2d2 = Droid
  { name            = "R2-D2"
  , friends         = [upcast luke]
  , appearsIn       = [NEWHOPE, EMPIRE, JEDI]
  , primaryFunction = "repair ships"
  }

c3po :: Droid
c3po = Droid
  { name            = "C3PO"
  , friends         = [upcast r2d2, upcast luke]
  , appearsIn       = [NEWHOPE, EMPIRE, JEDI]
  , primaryFunction = "protocol and human relations"
  }

getName :: HasField' "name" r a => r -> a
getName = getField @"name"

-- upcast :: Subtype a b => a -> b
characters :: [Character]
characters = [upcast r2d2, upcast luke, upcast c3po]

names :: [String]
names = map getName characters
-- => ["R2-D2","Luke Skywalker","C3PO"]