{-# LANGUAGE GADTs #-}
module Language.SQL.SQLite.Tools (
                                  changeTableSchema
                                 )
    where

import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set

import Language.SQL.SQLite.Syntax
import Language.SQL.SQLite.Types

changeTableSchema
    :: CreateTable
    -> CreateTable
    -> (Map UnqualifiedIdentifier Expression)
    -> StatementList
changeTableSchema oldDefinition newDefinition columnDefaultMap
    = let tableColumnNames :: CreateTable -> Set UnqualifiedIdentifier
          tableColumnNames (CreateTable _ _ _ (ColumnsAndConstraints columns _))
              = Set.fromList $ map columnDefinitionName $ fromOneOrMore columns
          columnDefinitionName (ColumnDefinition name _ _) = name
          oldColumns = tableColumnNames oldDefinition
          newColumns = tableColumnNames newDefinition
          removedColumns = Set.difference oldColumns newColumns
          addedColumns = Set.difference newColumns oldColumns
          preservedColumns = Set.intersection oldColumns newColumns
          tableName :: CreateTable -> SinglyQualifiedIdentifier
          tableName (CreateTable _ _ name _) = name
          oldName = tableName oldDefinition
          newName = tableName newDefinition
          temporaryName = SinglyQualifiedIdentifier Nothing
                          $ "temporary_" ++ (identifierProperName oldName)
                            ++ "_redefined_as_" ++ (identifierProperName newName)
          columnDefault column = maybe ExpressionLiteralNull
                                       id
                                       $ Map.lookup column columnDefaultMap
          beginTransactionStatement = Begin NoTransactionType Transaction
          createTemporaryStatement
              = CreateTable
                  Temporary
                  NoIfNotExists
                  temporaryName
                  (AsSelect
                   $ Select (SelectCore NoDistinctness
                                        (fromJust $ mkOneOrMore [Star])
                                        (Just
                                         $ From
                                         $ JoinSource (TableSource oldName
                                                                   NoAs
                                                                   NoIndexedBy)
                                                      [])
                                        Nothing
                                        Nothing)
                            [] Nothing Nothing)
          dropStatement = DropTable NoIfExists oldName
          recreateStatement = newDefinition
          reinsertStatement
              = Insert InsertNoAlternative
                       newName
                       $ InsertSelect
                             (concat [Set.toList preservedColumns,
                                      Set.toList addedColumns])
                             (Select (SelectCore NoDistinctness
                                                 (fromJust
                                                  $ mkOneOrMore
                                                  $ map (\result -> Result result NoAs)
                                                  $ concat
                                                    [map (ExpressionIdentifier
                                                          . toDoublyQualifiedIdentifier)
                                                     $ Set.toList preservedColumns,
                                                     map columnDefault
                                                         $ Set.toList addedColumns])
                                                 (Just
                                                  $ From
                                                  $ JoinSource
                                                    (TableSource temporaryName
                                                                 NoAs
                                                                 NoIndexedBy)
                                                    [])
                                                 Nothing
                                                 Nothing)
                                     [] Nothing Nothing)
          dropTemporaryStatement = DropTable NoIfExists temporaryName
          commitTransactionStatement = Commit CommitCommit Transaction
          statementList = StatementList [Statement beginTransactionStatement,
                                         Statement createTemporaryStatement,
                                         Statement dropStatement,
                                         Statement recreateStatement,
                                         Statement reinsertStatement,
                                         Statement dropTemporaryStatement,
                                         Statement commitTransactionStatement]
      in statementList