module Database.PostgreSQL.PQTypes.Model.Index (
    TableIndex(..)
  , IndexMethod(..)
  , tblIndex
  , indexOnColumn
  , indexOnColumns
  , indexOnColumnWithMethod
  , indexOnColumnsWithMethod
  , uniqueIndexOnColumn
  , uniqueIndexOnColumnWithCondition
  , uniqueIndexOnColumns
  , indexName
  , sqlCreateIndex
  , sqlCreateIndexSequentially
  , sqlCreateIndexConcurrently
  , sqlDropIndex
  ) where
import Crypto.Hash.RIPEMD160
import Data.ByteString.Base16
import Data.Char
import Data.Monoid
import Data.Monoid.Utils
import Database.PostgreSQL.PQTypes
import Prelude
import qualified Data.ByteString.Char8 as BS
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
data TableIndex = TableIndex {
  idxColumns :: [RawSQL ()]
, idxMethod  :: IndexMethod
, idxUnique  :: Bool
, idxValid   :: Bool 
                     
                     
, idxWhere   :: Maybe (RawSQL ())
} deriving (Eq, Ord, Show)
data IndexMethod =
    BTree
  | GIN
  deriving (Eq, Ord)
instance Show IndexMethod where
    show BTree = "btree"
    show GIN   = "gin"
instance Read IndexMethod where
    readsPrec _ (map toLower -> "btree") = [(BTree,"")]
    readsPrec _ (map toLower -> "gin")   = [(GIN,"")]
    readsPrec _ _       = []
tblIndex :: TableIndex
tblIndex = TableIndex {
  idxColumns = []
, idxMethod = BTree
, idxUnique = False
, idxValid = True
, idxWhere = Nothing
}
indexOnColumn :: RawSQL () -> TableIndex
indexOnColumn column = tblIndex { idxColumns = [column] }
indexOnColumnWithMethod :: RawSQL () -> IndexMethod -> TableIndex
indexOnColumnWithMethod column method =
    tblIndex { idxColumns = [column]
             , idxMethod = method }
indexOnColumns :: [RawSQL ()] -> TableIndex
indexOnColumns columns = tblIndex { idxColumns = columns }
indexOnColumnsWithMethod :: [RawSQL ()] -> IndexMethod -> TableIndex
indexOnColumnsWithMethod columns method =
    tblIndex { idxColumns = columns
             , idxMethod = method }
uniqueIndexOnColumn :: RawSQL () -> TableIndex
uniqueIndexOnColumn column = TableIndex {
  idxColumns = [column]
, idxMethod = BTree
, idxUnique = True
, idxValid = True
, idxWhere = Nothing
}
uniqueIndexOnColumns :: [RawSQL ()] -> TableIndex
uniqueIndexOnColumns columns = TableIndex {
  idxColumns = columns
, idxMethod = BTree
, idxUnique = True
, idxValid = True
, idxWhere = Nothing
}
uniqueIndexOnColumnWithCondition :: RawSQL () -> RawSQL () -> TableIndex
uniqueIndexOnColumnWithCondition column whereC = TableIndex {
  idxColumns = [column]
, idxMethod = BTree
, idxUnique = True
, idxValid = True
, idxWhere = Just whereC
}
indexName :: RawSQL () -> TableIndex -> RawSQL ()
indexName tname TableIndex{..} = flip rawSQL () $ T.take 63 . unRawSQL $ mconcat [
    if idxUnique then "unique_idx__" else "idx__"
  , tname
  , "__"
  , mintercalate "__" $ map (asText sanitize) idxColumns
  , maybe "" (("__" <>) . hashWhere) idxWhere
  ]
  where
    asText f = flip rawSQL () . f . unRawSQL
    
    
    sanitize = T.pack . foldr go [] . T.unpack
      where
        go c acc = if isAlphaNum c || c == '_'
          then c : acc
          else case acc of
            ('$':_) ->       acc
            _       -> '$' : acc
    
    
    hashWhere = asText $ T.decodeUtf8 . encode . BS.take 10 . hash . T.encodeUtf8
{-# DEPRECATED sqlCreateIndex "Use sqlCreateIndexSequentially instead" #-}
sqlCreateIndex :: RawSQL () -> TableIndex -> RawSQL ()
sqlCreateIndex = sqlCreateIndex_ False
sqlCreateIndexSequentially :: RawSQL () -> TableIndex -> RawSQL ()
sqlCreateIndexSequentially = sqlCreateIndex_ False
sqlCreateIndexConcurrently :: RawSQL () -> TableIndex -> RawSQL ()
sqlCreateIndexConcurrently = sqlCreateIndex_ True
sqlCreateIndex_ :: Bool -> RawSQL () -> TableIndex -> RawSQL ()
sqlCreateIndex_ concurrently tname idx@TableIndex{..} = mconcat [
    "CREATE"
  , if idxUnique then " UNIQUE" else ""
  , " INDEX "
  , if concurrently then "CONCURRENTLY " else ""
  , indexName tname idx
  , " ON" <+> tname
  , " USING" <+> (rawSQL (T.pack . show $ idxMethod) ()) <+> "("
  , mintercalate ", " idxColumns
  , ")"
  , maybe "" (" WHERE" <+>) idxWhere
  ]
sqlDropIndex :: RawSQL () -> TableIndex -> RawSQL ()
sqlDropIndex tname idx = "DROP INDEX" <+> indexName tname idx