{-
 - Copyright 2011-2014 Per Magnus Therning
 -
 - Licensed under the Apache License, Version 2.0 (the "License");
 - you may not use this file except in compliance with the License.
 - You may obtain a copy of the License at
 -
 -     http://www.apache.org/licenses/LICENSE-2.0
 -
 - Unless required by applicable law or agreed to in writing, software
 - distributed under the License is distributed on an "AS IS" BASIS,
 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 - See the License for the specific language governing permissions and
 - limitations under the License.
 -}

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module OldPkgDB
       ( CblPkg
       , pkgName
       , pkgPkg
       , pkgVersion
       , pkgXRev
       , pkgDeps
       , pkgFlags
       , pkgRelease
         --
       , isGhcPkg
       , isDistroPkg
       , isRepoPkg
       , isBasePkg
         --
       , createGhcPkg
       , createDistroPkg
       , createRepoPkg
       , createCblPkg
         --
       , CblDB
       , addPkg
       , addPkg2
       , delPkg
       , bumpRelease
       , lookupPkg
       , transitiveDependants
       , checkDependants
       , checkAgainstDb
       , saveDb
       , readDb
       ) where

-- {{{1 imports
import           Control.Arrow
import           Control.Exception as CE
import           Control.Monad
import           Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as C
import           Data.List
import           Data.Maybe
import qualified Distribution.Package as P
import           Distribution.PackageDescription
import qualified Distribution.Version as V
import           System.IO.Error

import qualified Util.Dist
import OldPkgTypes

-- {{{1 packages
pkgName :: CblPkg -> String
pkgName (CP n _) = n

pkgPkg :: CblPkg -> Pkg
pkgPkg (CP _ p) = p

pkgVersion :: CblPkg -> V.Version
pkgVersion (CP _ (GhcPkg d)) = gpVersion d
pkgVersion (CP _ (DistroPkg d)) = dpVersion d
pkgVersion (CP _ (RepoPkg d)) = rpVersion d

pkgXRev :: CblPkg -> Int
pkgXRev (CP _ (DistroPkg d)) = dpXrev d
pkgXRev (CP _ (RepoPkg d)) = rpXrev d
pkgXRev _ = 0

pkgDeps :: CblPkg -> [P.Dependency]
pkgDeps (CP _ (RepoPkg d)) = rpDeps d
pkgDeps _ = []

pkgFlags :: CblPkg -> FlagAssignment
pkgFlags (CP _ (RepoPkg d)) = rpFlags d
pkgFlags _ = []

pkgRelease :: CblPkg -> Int
pkgRelease (CP _ (GhcPkg _)) = -1
pkgRelease (CP _ (DistroPkg d)) = dpRelease d
pkgRelease (CP _ (RepoPkg d)) = rpRelease d

createGhcPkg :: String -> V.Version -> CblPkg
createGhcPkg n v = CP n (GhcPkg $ GhcPkgD v)

createDistroPkg :: String -> V.Version -> Int -> Int -> CblPkg
createDistroPkg n v x r = CP n (DistroPkg (DistroPkgD v x r))

createRepoPkg :: String -> V.Version -> Int -> [P.Dependency] -> FlagAssignment -> Int -> CblPkg
createRepoPkg n v x d fa r = CP n (RepoPkg $ RepoPkgD v x d fa r)

createCblPkg :: PackageDescription -> FlagAssignment -> CblPkg
createCblPkg pd fa = createRepoPkg name version xrev deps fa 1
  where
    name = Util.Dist.pkgNameStr pd
    version = P.pkgVersion $ package pd
    xrev = Util.Dist.pkgXRev pd
    deps = buildDepends pd

getDependencyOn :: String -> CblPkg -> Maybe P.Dependency
getDependencyOn n p = find (\ d -> Util.Dist.depName d == n) (pkgDeps p)

isGhcPkg :: CblPkg -> Bool
isGhcPkg (CP _ GhcPkg {}) = True
isGhcPkg _ = False

isDistroPkg :: CblPkg -> Bool
isDistroPkg (CP _ DistroPkg {}) = True
isDistroPkg _ = False

isRepoPkg :: CblPkg -> Bool
isRepoPkg (CP _ RepoPkg {}) = True
isRepoPkg _ = False

isBasePkg :: CblPkg -> Bool
isBasePkg = not . isRepoPkg

-- {{{1 database
emptyPkgDB :: CblDB
emptyPkgDB = []

addPkg :: CblDB -> String -> Pkg -> CblDB
addPkg db n p = nubBy cmp newdb
  where
    cmp (CP n1 _) (CP n2 _) = n1 == n2
    newdb = CP n p:db

addPkg2 :: CblDB -> CblPkg -> CblDB
addPkg2 db (CP n p) = addPkg db n p

delPkg :: CblDB -> String -> CblDB
delPkg db n = filter (\ p -> n /= pkgName p) db

bumpRelease :: CblDB -> String -> CblDB
bumpRelease db n = maybe db (addPkg2 db . doBump) (lookupPkg db n)
  where
    doBump (CP n' (RepoPkg d)) = CP n' (RepoPkg d { rpRelease = rpRelease d + 1 })
    doBump p = p

lookupPkg :: CblDB -> String -> Maybe CblPkg
lookupPkg [] _ = Nothing
lookupPkg (p:db) n
  | n == pkgName p = Just p
  | otherwise = lookupPkg db n

lookupDependants :: [CblPkg] -> String -> [String]
lookupDependants db n = filter (/= n) $ map pkgName $ filter (`doesDependOn` n) db
  where
    doesDependOn p n' = n' `elem` map Util.Dist.depName (pkgDeps p)

transitiveDependants :: CblDB -> [String] -> [String]
transitiveDependants db names = keepLast $ concatMap transUsersOfOne names
  where
    transUsersOfOne n = n : transitiveDependants db (lookupDependants db n)
    keepLast = reverse . nub . reverse

checkDependants :: CblDB -> String -> V.Version -> [(String, Maybe P.Dependency)]
checkDependants db n v =
  let d1 = mapMaybe (lookupPkg db) (lookupDependants db n)
      -- d2 = map (\ p -> (pkgName p, getDependencyOn n p)) d1
      d2 = map (pkgName &&& getDependencyOn n ) d1
      fails = filter (not . V.withinRange v . Util.Dist.depVersionRange . fromJust . snd) d2
  in fails

checkAgainstDb :: CblDB -> String -> P.Dependency -> Bool
checkAgainstDb db name dep =
  let dN = Util.Dist.depName dep
      dVR = Util.Dist.depVersionRange dep
  in (dN == name) ||
     (case lookupPkg db dN of
         Nothing -> False
         Just p -> V.withinRange (pkgVersion p) dVR)

readDb :: FilePath -> IO CblDB
readDb fp = handle
  (\ e -> if isDoesNotExistError e
          then return emptyPkgDB
          else throwIO e)
  $ do r <- (mapM decode . C.lines) `liftM` C.readFile fp
       case r of
         Just a -> return a
         Nothing -> fail "JSON parsing failed"

saveDb :: CblDB -> FilePath -> IO ()
saveDb db fp = C.writeFile fp s
  where
    s = C.unlines $ map encode $ sort db
