{- |
   Module      :  Distribution.Hackage.DB.Parsed
   License     :  BSD3
   Maintainer  :  simons@cryp.to
   Stability   :  provisional
   Portability :  portable

   This module provides simple access to the Hackage database by means
   of 'Map'.
 -}

module Distribution.Hackage.DB.Parsed
  ( Hackage, readHackage, readHackage', parseHackage
  , parseUnparsedHackage
  , parsePackage, parsePackage'
  )
  where

import Data.ByteString.Lazy.Char8 ( ByteString )
import Data.Map
import Data.String.UTF8 ( toString, fromRep )
import Data.Version
import qualified Distribution.Hackage.DB.Unparsed as Unparsed
import Distribution.PackageDescription
import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) )
import Distribution.Text ( display )

-- | A 'Map' representation of the Hackage database. Every package name
-- maps to a non-empty set of version, and for every version there is a
-- parsed Cabal file.

type Hackage = Map String (Map Version GenericPackageDescription)

-- | Read the Hackage database from the location determined by 'hackagePath'
-- and return a 'Map' that provides fast access to its contents.

readHackage :: IO Hackage
readHackage = fmap parseUnparsedHackage Unparsed.readHackage

-- | Read the Hackage database from the given 'FilePath' and return a
-- 'Hackage' map that provides fast access to its contents.

readHackage' :: FilePath -> IO Hackage
readHackage' = fmap parseUnparsedHackage . Unparsed.readHackage'

-- | Parse the contents of Hackage's @00-index.tar@ into a 'Hackage' map.

parseHackage :: ByteString -> Hackage
parseHackage = parseUnparsedHackage . Unparsed.parseHackage

-- | Convert an 'Unparsed.Hackage' map into a parsed 'Hackage' map.

parseUnparsedHackage :: Unparsed.Hackage -> Hackage
parseUnparsedHackage = Data.Map.mapWithKey parsePackages
  where
    parsePackages :: String -> Map Version ByteString -> Map Version GenericPackageDescription
    parsePackages name = Data.Map.mapWithKey (parsePackage name)

-- | Convenience wrapper around 'parsePackage'' to parse a single Cabal
-- file. Failure is reported with 'error'.

parsePackage :: String -> Version -> ByteString -> GenericPackageDescription
parsePackage name version buf = case parsePackage' buf of
                           Right a  -> a
                           Left err -> error $ "cannot parse cabal package " ++ show name ++ "-" ++ display version ++ ": " ++ err

-- | Parse a single Cabal file.

parsePackage' :: ByteString -> Either String GenericPackageDescription
parsePackage' buf = case parsePackageDescription (decodeUTF8 buf) of
                     ParseOk _ a     -> Right a
                     ParseFailed err -> Left (show err)
  where
    decodeUTF8 :: ByteString -> String
    decodeUTF8 = toString . fromRep