-- | Some basic lenses for the Messages API. These are compatible with both lens and lens-family.
-- This package doesn't provide any lens combinators like @^.@ or @^..@, so you'll need to use
-- ones from a lens package.
--
-- For example, the following code gets the values of the 'speed' fields
-- from all of the 'record' messages in the file:
--
-- @
-- Right fit <- readFileMessages "file.fit"
-- let speeds = fit ^.. message 20 . field 6 . int
-- @
module Fit.Messages.Lens (
  -- * Messages
  messages,
  message,
  messageNumber,

  -- * Fields
  fields,
  field,
  fieldNumber,
  fieldValue,

  -- * Values
  -- $values
  int,
  real,
  text,
  byte,
  ints,
  reals,
  bytestring
  ) where

import Fit.Messages

import Control.Applicative ((<$>), pure, Applicative)
import Data.ByteString (ByteString)
import Data.Functor.Contravariant (Contravariant, contramap)
import qualified Data.IntMap as Map (filterWithKey)
import Data.Sequence (Seq)
import qualified Data.Sequence as S (filter)
import Data.Text (Text)
import Data.Traversable (traverse)
import Data.Word (Word8)

-- Helper for building Folds
coerce :: (Contravariant f, Applicative f) => f a -> f b
coerce = contramap (const ()) . fmap (const ())

-- | Traverse all the messages in a @Messages@
--
-- @messages :: Traversal' Messages Message@
messages :: Applicative f => (Message -> f Message) -> Messages -> f Messages
messages f ms =  Messages <$> traverse f (_messages ms)
{-# INLINE messages #-}

-- | A Fold over the messages with the given message number
--
-- @message :: Int -> Fold Messages Message@
message :: (Contravariant f, Applicative f) => Int -> (Message -> f Message) -> Messages -> f Messages
message msgNum f ms = coerce (traverse f targets)
  where targets = S.filter ((== msgNum) . _mNumber) (_messages ms)
{-# INLINE message #-}

-- | Lens on the message number from a @Message@
--
-- @messageNumber :: Lens' Message Int@
messageNumber :: Functor f => (Int -> f Int) -> Message -> f Message
messageNumber f m = (\n -> m { _mNumber = n }) <$> f (_mNumber m)
{-# INLINE messageNumber #-}

-- | Traverse all the fields in a @Message@
--
-- @fields :: Traversal' Message Field@
fields :: Applicative f => (Field -> f Field) -> Message -> f Message
fields f (Message n flds) = Message n <$> traverse f flds
{-# INLINE fields #-}

-- | A Fold over the fields in a @Message@ with the given field number
--
-- @field :: Int -> Fold Message Field@
field :: (Contravariant f, Applicative f) => Int -> (Field -> f Field) -> Message -> f Message
field n f msg = coerce (traverse f targetFields)
  where targetFields = Map.filterWithKey (\k _ -> k == n) (_mFields msg)
{-# INLINE field #-}

-- | Lens on the field number from a @Field@
--
-- @fieldNumber :: Lens Field Int@
fieldNumber :: Functor f => (Int -> f Int) -> Field -> f Field
fieldNumber f fld = (\n -> fld { _fNumber = n }) <$> f (_fNumber fld)
{-# INLINE fieldNumber #-}

-- | Lens on the @Value@ from a @Field@
--
-- @fieldValue :: Lens Field Value@
fieldValue :: Functor f => (Value -> f Value) -> Field -> f Field
fieldValue f fld = (\v -> fld { _fValue = v }) <$> f (_fValue fld)
{-# INLINE fieldValue #-}


-- $values
-- Generally when you're looking up the value for a particular field you'll know
-- the expected type ahead of time. If you know the field you're looking at holds
-- integers, then you can use @int@ to directly get an @Int@ instead of a
-- @Singleton (IntValue x)@.
--
-- These traversals are not prisms, because to reconstruct the @Field@ we need
-- the field number in addition to the wrapped value.

-- | Traverse the @Singleton@ and @IntValue@ constructors for a field value
--
-- @int :: Traversal' Field Int@
int :: Applicative f => (Int -> f Int) -> Field -> f Field
int f (Field n (Singleton (IntValue i))) = Field n . Singleton . IntValue <$> f i
int _ fld = pure fld
{-# INLINE int #-}

-- | Traverse the @Singleton@ and @RealValue@ constructors for a field value
--
-- @real :: Traversal' Field Double@
real :: Applicative f => (Double -> f Double) -> Field -> f Field
real f (Field n (Singleton (RealValue d))) = Field n . Singleton . RealValue <$> f d
real _ fld = pure fld
{-# INLINE real #-}

-- | Traverse the @Singleton@ and @TextValue@ constructors for a field value
--
-- @text :: Traversal' Field Text@
text :: Applicative f => (Text -> f Text) -> Field -> f Field
text f (Field n (Singleton (TextValue t))) = Field n . Singleton . TextValue <$> f t
text _ fld = pure fld
{-# INLINE text #-}

-- | Traverse the @Singleton@ and @ByteValue@ constructors for a field value
--
-- @byte :: Traversal' Field Word8@
byte :: Applicative f => (Word8 -> f Word8) -> Field -> f Field
byte f (Field n (Singleton (ByteValue b))) = Field n . Singleton . ByteValue <$> f b
byte _ fld = pure fld
{-# INLINE byte #-}

-- | Traverse the @Array@ and @IntArray@ constructors for a field value
--
-- @ints :: Traversal' Field (Seq Int)@
ints :: Applicative f => (Seq Int -> f (Seq Int)) -> Field -> f Field
ints f (Field n (Array (IntArray s))) = Field n . Array . IntArray <$> f s
ints _ fld = pure fld
{-# INLINE ints #-}

-- | Traverse the @Array@ and @RealArray@ constructors for a field value
--
-- @reals :: Traversal' Field (Seq Double)@
reals :: Applicative f => (Seq Double -> f (Seq Double)) -> Field -> f Field
reals f (Field n (Array (RealArray s))) = Field n . Array . RealArray <$> f s
reals _ fld = pure fld
{-# INLINE reals #-}

-- | Travese the @Array@ and @ByteArray@ constructors for a field value
--
-- @bytestring :: Traversal' Field ByteString@
bytestring :: Applicative f => (ByteString -> f ByteString) -> Field -> f Field
bytestring f (Field n (Array (ByteArray bs))) = Field n . Array . ByteArray <$> f bs
bytestring _ fld = pure fld
{-# INLINE bytestring #-}