{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- |
-- Module:       $HEADER$
-- Description:  Data type describing pkg-config configuration file
-- Copyright:    (c) 2014 Peter Trsko
-- License:      BSD3
--
-- Maintainer:   peter.trsko@gmail.com
-- Stability:    experimental
-- Portability:  NoImplicitPrelude
--
-- Data type describing /pkg-config/ configuration file.
module Data.PkgConfig.Internal.PkgConfig
    (
    -- * PkgConfig
      PkgConfig(..)

    -- * Type Aliases
    , PkgVariable
    , PkgName
    , PkgDescription
    , PkgUrl
    , PkgVersion

    -- * Lenses
    , pkgVariables
    , pkgName
    , pkgDescription
    , pkgUrl
    , pkgVersion
    , pkgRequires
    , pkgRequiresPrivate
    , pkgConflicts
    , pkgCflags
    , pkgLibs
    , pkgLibsPrivate

    -- * Serialization
    , toStrictText
    )
  where

import Data.Bool (not, otherwise)
import Data.Data (Data)
import Data.Function ((.), ($), flip)
import Data.Functor (Functor(fmap))
import Data.List as List (filter, map)
import Data.Monoid ((<>))
import Data.Typeable (Typeable)
import Text.Show (Show)

import qualified Data.Text as Strict (Text)
import qualified Data.Text as Strict.Text

import Data.Default.Class (Default(def))

import Data.PkgConfig.Internal.Template (PkgTemplate)
import qualified Data.PkgConfig.Internal.Template as Template (toStrictText)


-- | Variable definition consisting of its name and value in form of
-- 'PkgTemplate'.
type PkgVariable = (Strict.Text, PkgTemplate)

type PkgName = Strict.Text
type PkgDescription = Strict.Text
type PkgUrl = Strict.Text

-- | Package version may use variable expansion and so it is represented by
-- 'PkgConfig'.
type PkgVersion = PkgTemplate

-- | Representation of /pkg-config/ configuration file.
data PkgConfig = PkgConfig
    { _pkgVariables :: [PkgVariable]
    -- ^ Variable definitions.

    , _pkgName :: PkgName
    -- ^ Human-readable name for a library or package. This field is not used
    -- by /pkg-config/ tool for queries, because it uses @.pc@ file base name.

    , _pkgDescription :: PkgDescription
    -- ^ Brief description of the package.

    , _pkgUrl :: PkgUrl
    -- ^ URL where people can get more information about and download the
    -- package.

    , _pkgVersion :: PkgVersion
    -- ^ Version of the package.

    , _pkgRequires :: PkgTemplate
    -- ^ List of packages required by this package and their version bounds.

    , _pkgRequiresPrivate :: PkgTemplate
    -- ^ List of private packages required by this package but not exposed to
    -- applications. The version specific rules from the Requires field also
    -- apply here.

    , _pkgConflicts :: PkgTemplate
    -- ^ An optional field describing packages that this one conflicts with.
    -- The version specific rules from the Requires field also apply here. This
    -- field also takes multiple instances of the same package. E.g.:
    --
    -- @
    -- Conflicts: bar \< 1.2.3, bar \>= 1.3.0.
    -- @

    , _pkgCflags :: PkgTemplate
    -- ^ Compiler flags specific to this package and any required libraries
    -- that don't support /pkg-config/. If the required libraries support
    -- /pkg-config/, they should be added to @Requires@ ('_pkgRequires') or
    -- @Requires.private@ ('_pkgRequiresPrivate').

    , _pkgLibs :: PkgTemplate
    -- ^ Linking flags specific to this package and any required libraries that
    -- don't support /pkg-config/. The same rules as for @Cflags@ ('_pkgCflags')
    -- field apply here.

    , _pkgLibsPrivate :: PkgTemplate
    -- ^ Linking flags for private libraries required by this package but not
    -- exposed to applications. The same rules as for @Cflags@ ('_pkgCflags') field apply
    -- here.
    }
  deriving (Data, Show, Typeable)

instance Default PkgConfig where
    def = PkgConfig
        { _pkgVariables = []
        , _pkgName = Strict.Text.empty
        , _pkgDescription = Strict.Text.empty
        , _pkgUrl = Strict.Text.empty
        , _pkgVersion = def
        , _pkgRequires = def
        , _pkgRequiresPrivate = def
        , _pkgConflicts = def
        , _pkgCflags = def
        , _pkgLibs = def
        , _pkgLibsPrivate = def
        }

-- | Serialize 'PkgConfig' in to strict 'Strict.Text'.
toStrictText :: PkgConfig -> Strict.Text
toStrictText PkgConfig{..} = Strict.Text.concat
    [ variablesToStrictText _pkgVariables
    , Strict.Text.unlines [""]
    , Strict.Text.unlines $ List.filter (not . Strict.Text.null)
        [ "Name"             <:>  _pkgName
        , "Description"      <:>  _pkgDescription
        , "URL"              <:>  _pkgUrl
        , "Version"          <:>. _pkgVersion
        , "Requires"         <:>. _pkgRequires
        , "Requires.private" <:>. _pkgRequiresPrivate
        , "Conflicts"        <:>. _pkgConflicts
        , "Cflags"           <:>. _pkgCflags
        , "Libs"             <:>. _pkgLibs
        , "Libs.private"     <:>. _pkgLibsPrivate
        ]
    ]
  where
    key <:> value
      | Strict.Text.null value = Strict.Text.empty
      | otherwise              = key <> ": " <> value

    key <:>. value = key <:> Template.toStrictText value

    variablesToStrictText = Strict.Text.unlines . map varToText
      where
        varToText (name, tmpl) = name <> "=" <> Template.toStrictText tmpl

(<$$>) :: Functor f => f a -> (a -> b) -> f b
(<$$>) = flip fmap

-- | Variable definitions.
pkgVariables
    :: Functor f
    => ([PkgVariable] -> f [PkgVariable])
    -> PkgConfig -> f PkgConfig
pkgVariables f cfg@(PkgConfig{_pkgVariables = a}) =
    f a <$$> \b -> cfg{_pkgVariables = b}

-- | Human-readable name of a library or package. This field is not used by
-- /pkg-config/ tool for queries, because it uses @.pc@ file base name.
pkgName
    :: Functor f
    => (Strict.Text -> f Strict.Text)
    -> PkgConfig -> f PkgConfig
pkgName f cfg@(PkgConfig{_pkgName = a}) =
    f a <$$> \b -> cfg{_pkgName = b}

-- | Brief description of the package.
pkgDescription
    :: Functor f
    => (Strict.Text -> f Strict.Text)
    -> PkgConfig -> f PkgConfig
pkgDescription f cfg@(PkgConfig{_pkgDescription = a}) =
    f a <$$> \b -> cfg{_pkgDescription = b}

-- | URL where people can get more information about and download the package.
pkgUrl
    :: Functor f
    => (Strict.Text -> f Strict.Text)
    -> PkgConfig -> f PkgConfig
pkgUrl f cfg@(PkgConfig{_pkgUrl = a}) =
    f a <$$> \b -> cfg{_pkgUrl = b}

-- | Version of the package.
pkgVersion
    :: Functor f
    => (PkgTemplate -> f PkgTemplate)
    -> PkgConfig -> f PkgConfig
pkgVersion f cfg@(PkgConfig{_pkgVersion = a}) =
    f a <$$> \b -> cfg{_pkgVersion = b}

-- | List of packages required by this package and their version bounds.
pkgRequires
    :: Functor f
    => (PkgTemplate -> f PkgTemplate)
    -> PkgConfig -> f PkgConfig
pkgRequires f cfg@(PkgConfig{_pkgRequires = a}) =
    f a <$$> \b -> cfg{_pkgRequires = b}

-- | Compiler flags specific to this package and any required libraries that
-- don't support /pkg-config/. If the required libraries support /pkg-config/,
-- they should be added to @Requires@ ('pkgRequires') or @Requires.private@
-- ('pkgRequiresPrivate').
pkgRequiresPrivate
    :: Functor f
    => (PkgTemplate -> f PkgTemplate)
    -> PkgConfig -> f PkgConfig
pkgRequiresPrivate f cfg@(PkgConfig{_pkgRequiresPrivate = a}) =
    f a <$$> \b -> cfg{_pkgRequiresPrivate = b}

-- | An optional field describing packages that this one conflicts with. The
-- version specific rules from the Requires field also apply here. This field
-- also takes multiple instances of the same package. E.g.:
--
-- @
-- Conflicts: bar \< 1.2.3, bar \>= 1.3.0.
-- @
pkgConflicts
    :: Functor f
    => (PkgTemplate -> f PkgTemplate)
    -> PkgConfig -> f PkgConfig
pkgConflicts f cfg@(PkgConfig{_pkgConflicts = a}) =
    f a <$$> \b -> cfg{_pkgConflicts = b}

-- | Compiler flags specific to this package and any required libraries that
-- don't support /pkg-config/. If the required libraries support /pkg-config/,
-- they should be added to @Requires@ ('pkgRequires') or @Requires.private@
-- ('pkgRequiresPrivate').
pkgCflags
    :: Functor f
    => (PkgTemplate -> f PkgTemplate)
    -> PkgConfig -> f PkgConfig
pkgCflags f cfg@(PkgConfig{_pkgCflags = a}) =
    f a <$$> \b -> cfg{_pkgCflags = b}

-- | Linking flags specific to this package and any required libraries that
-- don't support /pkg-config/. The same rules as for @Cflags@ ('pkgCflags')
-- field apply here.
pkgLibs
    :: Functor f
    => (PkgTemplate -> f PkgTemplate)
    -> PkgConfig -> f PkgConfig
pkgLibs f cfg@(PkgConfig{_pkgLibs = a}) =
    f a <$$> \b -> cfg{_pkgLibs = b}

-- | Linking flags for private libraries required by this package but not
-- exposed to applications. The same rules as for @Cflags@ ('pkgCflags') field
-- apply here.
pkgLibsPrivate
    :: Functor f
    => (PkgTemplate -> f PkgTemplate)
    -> PkgConfig -> f PkgConfig
pkgLibsPrivate f cfg@(PkgConfig{_pkgLibsPrivate = a}) =
    f a <$$> \b -> cfg{_pkgLibsPrivate = b}