module Lorentz.Run
  ( compileLorentz
  , compileLorentzContract
  , interpretLorentzInstr
  , interpretLorentzLambda
  , analyzeLorentz
  ) where

import Data.Constraint ((\\))
import Data.Vinyl.Core (Rec(..))
import Fmt (pretty)

import Lorentz.Base
import Lorentz.Constraints
import Lorentz.EntryPoints
import Michelson.Analyzer (AnalyzerRes, analyze)
import Michelson.Interpret
import Michelson.Typed
  (FullContract(..), Instr(..), IsoValue, IsoValuesStack(..), ToT, ToTs, isStar,
  mkParamNotes, starNotes)

-- | For use outside of Lorentz.
compileLorentz :: (inp :-> out) -> Instr (ToTs inp) (ToTs out)
compileLorentz = iAnyCode

-- | Version of 'compileLorentz' specialized to instruction corresponding to
-- contract code.
compileLorentzContract
  :: forall cp st.
     (NiceParameter cp, NiceStorage st, ParameterEntryPoints cp)
  => Contract cp st -> FullContract (ToT cp) (ToT st)
compileLorentzContract contract =
  FullContract
  { fcCode = if isStar cpNotes
      then  -- We have no annotations. Print contract as such.
        compileLorentz contract
      else  -- If we have some annotations, erase them.
        compileLorentz (I CAST # contract :: Contract cp st)
  , fcParamNotesSafe = case mkParamNotes cpNotes of
      Right n -> n
      Left e -> error $
        -- TODO [TM-280]: make sure Lorentz actually does not produce such
        "Lorentz unexpectedly compiled into contract with \
        \illegal parameter declaration: " <> show cpNotes <> "\n" <>
        "Reason: " <> pretty e
  , fcStoreNotes = starNotes
  } \\ niceParameterEvi @cp
    \\ niceStorageEvi @st
  where
    cpNotes = pesNotes (parameterEntryPoints @cp)

-- | Interpret a Lorentz instruction, for test purposes.
interpretLorentzInstr
  :: (IsoValuesStack inp, IsoValuesStack out)
  => ContractEnv
  -> inp :-> out
  -> Rec Identity inp
  -> Either MichelsonFailed (Rec Identity out)
interpretLorentzInstr env (compileLorentz -> instr) inp =
  fromValStack <$> interpretInstr env instr (toValStack inp)

-- | Like 'interpretLorentzInstr', but works on lambda rather than
-- arbitrary instruction.
interpretLorentzLambda
  :: (IsoValue inp, IsoValue out)
  => ContractEnv
  -> Lambda inp out
  -> inp
  -> Either MichelsonFailed out
interpretLorentzLambda env instr inp = do
  res <- interpretLorentzInstr env instr (Identity inp :& RNil)
  let Identity out :& RNil = res
  return out

-- | Lorentz version of analyzer.
analyzeLorentz :: inp :-> out -> AnalyzerRes
analyzeLorentz = analyze . compileLorentz