{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE UndecidableInstances #-}

--
-- Copyright (c) 2011   Stefan Wehr - http://www.stefanwehr.de
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
--

module Test.Framework.CriterionWrapper (

    HtfBenchmark, ComparisonBenchmark
  , mkComparison, mkComparisonWithMargin
  , simpleBenchmark, withBenchmarkConfig
  , withBenchmarkComparison, withBenchmarkComparisonAndConfig
  , IsHtfBenchmark, asHtfBenchmark, prepareHtfBenchmark
  , defaultBenchmarkConfig

) where

--import qualified Data.Vector.Unboxed as V
--import Control.Monad.Trans

import Data.Maybe (isJust)

import Criterion
import Criterion.Monad
import Criterion.Environment
import Criterion.Config

import Test.Framework.TestConfig

data AnyBenchmarkable = forall b . Benchmarkable b => AnyBenchmarkable b

instance Benchmarkable AnyBenchmarkable where
    run (AnyBenchmarkable b) n = run b n

data HtfBenchmark
    = HtfBenchmark
      { htfb_benchmarkable :: AnyBenchmarkable
      , htfb_config :: Config
      , htfb_comparison :: Maybe ComparisonBenchmark
      , htfb_pending :: Bool
      }

instance Benchmarkable HtfBenchmark where
    run b n = run (htfb_benchmarkable b) n

data ComparisonBenchmark
    = ComparisonBenchmark
      { cb_benchmarkable :: AnyBenchmarkable
      , cb_factor :: Double
      , cb_margin :: Double
      }

defaultBenchmarkConfig :: Config
defaultBenchmarkConfig = defaultConfig

mkComparison :: Benchmarkable b => b -> Double -> ComparisonBenchmark
mkComparison b f = mkComparisonWithMargin b f 0.1

mkComparisonWithMargin :: Benchmarkable b => b -> Double -> Double -> ComparisonBenchmark
mkComparisonWithMargin b f m = ComparisonBenchmark (AnyBenchmarkable b) f m

simpleBenchmark :: Benchmarkable b => b -> HtfBenchmark
simpleBenchmark b =
    HtfBenchmark
    {
      htfb_benchmarkable = AnyBenchmarkable b
    , htfb_config = defaultConfig
    , htfb_comparison = Nothing
    , htfb_pending = False
    }

withBenchmarkConfig :: Benchmarkable b => Config -> b -> HtfBenchmark
withBenchmarkConfig cfg b =
    HtfBenchmark
    {
      htfb_benchmarkable = AnyBenchmarkable b
    , htfb_config = cfg
    , htfb_comparison = Nothing
    , htfb_pending = False
    }

withBenchmarkComparison :: Benchmarkable b => ComparisonBenchmark -> b -> HtfBenchmark
withBenchmarkComparison cmp b =
    HtfBenchmark
    {
      htfb_benchmarkable = AnyBenchmarkable b
    , htfb_config = defaultConfig
    , htfb_comparison = Just cmp
    , htfb_pending = False
    }

withBenchmarkComparisonAndConfig ::
    Benchmarkable b => ComparisonBenchmark -> Config -> b -> HtfBenchmark
withBenchmarkComparisonAndConfig cmp cfg b =
    HtfBenchmark
    {
      htfb_benchmarkable = AnyBenchmarkable b
    , htfb_config = cfg
    , htfb_comparison = Just cmp
    , htfb_pending = False
    }

{-
getSamples :: Benchmarkable b => b -> IO [Double]
getSamples b =
    do samples <- withConfig defaultConfig $ do env <- measureEnvironment
                                                liftIO $ putStrLn "running benchmark"
                                                runBenchmark env b
       return $ V.toList samples
-}

class IsHtfBenchmark a where
    isHtfBenchmark :: a -> Maybe HtfBenchmark

instance IsHtfBenchmark HtfBenchmark where
    isHtfBenchmark = Just

instance Benchmarkable b => IsHtfBenchmark b where
    isHtfBenchmark = Just . simpleBenchmark

asHtfBenchmark :: (Benchmarkable b, IsHtfBenchmark b) => b -> HtfBenchmark
asHtfBenchmark b =
    case isHtfBenchmark b of
      Nothing -> simpleBenchmark b
      Just h -> h

prepareHtfBenchmark :: HtfBenchmark -> (TestConfig -> Bool, IO ())
prepareHtfBenchmark bench =
    (\tc -> tc_benchmarks tc || isJust (htfb_comparison bench),
     putStrLn "running benchmarks not yet implemented")

benchmarkPending :: (Benchmarkable b, IsHtfBenchmark b) => b -> HtfBenchmark
benchmarkPending x =
    (asHtfBenchmark x) { htfb_pending = True }