{-# LANGUAGE CPP #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

module Hercules.CNix.Expr.Raw
  ( RawValue (..),
    mkRawValue,
    RawValueType (..),
    rawValueType,
    forceValue,
    canonicalRawType,
  )
where

import Hercules.CNix.Expr.Context
import qualified Language.C.Inline.Cpp as C
import qualified Language.C.Inline.Cpp.Exception as C
import Protolude hiding (evalState)
import Prelude ()

C.context context

C.include "<nix/expr/eval.hh>"
C.include "<nix/expr/eval-inline.hh>"

C.include "<hercules-ci-cnix/expr.hxx>"
C.include "<gc/gc.h>"
C.include "<gc/gc_cpp.h>"
C.include "<gc/gc_allocator.h>"

C.using "namespace nix"

-- | A heap object.
--
-- Nix doesn't store all its objects on the heap, but we do.
--
-- Also, Nix calls them @Value@s but it includes thunks, which are not values
-- and some may never produce values, such as @throw "msg"@.
newtype RawValue = RawValue (Ptr Value')

-- | Takes ownership of the value.
mkRawValue :: Ptr Value' -> IO RawValue
mkRawValue p = pure $ RawValue p

-- | Similar to Nix's Value->type but conflates the List variations
data RawValueType
  = Int
  | Bool
  | String
  | Path
  | Null
  | Attrs
  | List
  | Thunk
  | App
  | Lambda
  | Blackhole
  | PrimOp
  | PrimOpApp
  | External
  | Float
  | Other
  deriving (Generic, Show, Eq, Ord)

-- | You may need to 'forceValue' first.
rawValueType :: RawValue -> IO RawValueType
rawValueType (RawValue v) =
  f
    <$> [C.block| int {
      switch ($(Value* v)->type()) {
        case nInt:         return 1;
        case nBool:        return 2;
        case nString:      return 3;
        case nPath:        return 4;
        case nNull:        return 5;
        case nAttrs:       return 6;
        case nList:        return 7;
        case nFunction:    return 8;
        case nExternal:    return 9;
        case nFloat:       return 10;
        case nThunk:       return 11;
        default: return 0;
      }
    }|]
  where
    f 1 = Int
    f 2 = Bool
    f 3 = String
    f 4 = Path
    f 5 = Null
    f 6 = Attrs
    f 7 = List
    f 8 = Lambda
    f 9 = External
    f 10 = Float
    f 11 = Thunk
    f _ = Other

forceValue :: (Exception a) => Ptr EvalState -> RawValue -> IO (Either a ())
forceValue evalState (RawValue v) =
  try
    [C.catchBlock|  {
      Value *v = $(Value *v);
      if (v == NULL) throw std::invalid_argument("forceValue value must be non-null");
      $(EvalState *evalState)->forceValue(*v, nix::noPos);
    }|]

-- | Brings RawValueType closer to the 2.4 ValueType.
--
-- This function won't be necessary when support for 2.3 is dropped and we
-- switch entirely to the Haskell equivalent of C++ ValueType.
canonicalRawType :: RawValueType -> RawValueType
canonicalRawType = \case
  App -> Thunk
  Blackhole -> Thunk
  PrimOp -> Lambda
  PrimOpApp -> Lambda
  x -> x
