content: module Text.Seonbi.Hangul
      ( JamoTriple
      , fromJamoTriple
      , isHangulSyllable
      , toJamoTriple
      ) where
  
  -- $setup
  -- >>> import qualified Text.Show.Unicode
  -- >>> :set -interactive-print=Text.Show.Unicode.uprint
  
  -- | A triple of an initial consonant, a vowel, and an optional final consonant.
  type JamoTriple = (Char, Char, Maybe Char)
  
  -- | Checks if a character is a hangul letter and a complete syllable.
  --
  -- >>> isHangulSyllable '가'
  -- True
  -- >>> isHangulSyllable 'ㄱ'
  -- False
  isHangulSyllable :: Char -> Bool
  isHangulSyllable c =
      c >= '\xac00' && c <= '\xd7a3';
  
  syllableBase :: Int
  syllableBase = 0xac00
  
  initialBase :: Int
  initialBase = 0x1100
  
  vowelBase :: Int
  vowelBase = 0x1161
  
  finalBase :: Int
  finalBase = 0x11a7
  
  vowelCount :: Int
  vowelCount = 21;
  
  finalCount :: Int
  finalCount = 28;
  
  -- | Takes a complete hangul syllable apart into consonants and a vowel.
  -- Returns 'Nothing' for non-hangul letters.
  --
  -- >>> toJamoTriple '가'
  -- Just ('ᄀ','ᅡ',Nothing)
  -- >>> toJamoTriple '글'
  -- Just ('ᄀ','ᅳ',Just 'ᆯ')
  -- >>> toJamoTriple 'A'
  -- Nothing
  toJamoTriple :: Char -> Maybe JamoTriple
  toJamoTriple c
    | isHangulSyllable c = Just
        ( toEnum $ initialBase + ((syllable `div` finalCount) `div` vowelCount)
        , toEnum $ vowelBase + ((syllable `div` finalCount) `mod` vowelCount)
        , case syllable `mod` finalCount of
            0 -> Nothing
            f -> Just $ toEnum (finalBase + f)
        )
    | otherwise = Nothing
    where
      syllable :: Int
      syllable = fromEnum c - syllableBase
  
  -- | Composes hangul jamo triple into a hangul syllable.
  --
  -- >>> fromJamoTriple ('ᄀ', 'ᅡ', Nothing)
  -- Just '가'
  -- >>> fromJamoTriple ('ᄀ', 'ᅳ', Just 'ᆯ')
  -- Just '글'
  fromJamoTriple :: JamoTriple -> Maybe Char
  fromJamoTriple (initial, vowel, final)
    | initialIndex < 0 = Nothing
    | initialIndex > 18 = Nothing
    | vowelIndex < 0 = Nothing
    | vowelIndex > 20 = Nothing
    | finalIndex < 0 = Nothing
    | finalIndex > 27 = Nothing
    | otherwise = Just $ toEnum $ syllableBase +
        (initialIndex * vowelCount + vowelIndex) * finalCount + finalIndex
    where
      initialIndex :: Int
      initialIndex = fromEnum initial - initialBase
      vowelIndex :: Int
      vowelIndex = fromEnum vowel - vowelBase
      finalIndex :: Int
      finalIndex = maybe 0 (\ f -> fromEnum f - finalBase) final
  