    {-| Data type for a sequence position.

    Zero-based 'Offset' / 'Int64' indices are used throughout, to
    facilitate direct use of indexing functions on 'SeqData'.
    -}

    module Bio.Location.Position (
      -- * Sequence positions
      Pos(..)

      -- * Manipulating positions
      , slide

      -- * Extracting sequences
      , seqNt, seqNtPadded

      -- * Displaying positions
      , display )
        where

    import Control.Monad.Error
    import qualified Data.ByteString.Lazy.Char8 as LBS
    import Data.Ix (Ix)
    import Data.List

    import Bio.Location.Strand
    import Bio.Sequence.SeqData

    -- | Position in a sequence
    data Pos = Pos { offset :: !Offset -- ^ 0-based index of the position
                   , strand :: !Strand -- ^ Strand of the position
                   }
                  deriving (Eq, Ord, Show, Read, Ix)

    instance Stranded Pos where
        revCompl (Pos off str) = Pos off $ revCompl str

    -- | Returns a position resulting from sliding the original position
    -- along the sequence by a specified offset.  A positive offset will
    -- move the position away from the 5\' end of the forward stand of the
    -- sequence regardless of the strand of the position itself.  Thus,
    --
    -- > slide (revCompl pos) off == revCompl (slide pos off)

    slide :: Pos -> Offset -> Pos
    slide (Pos off str) doff = Pos (off + doff) str

    -- | Extract the nucleotide at a specific sequence position.  If the
    -- position lies outside the bounds of the sequence, an error results.
    seqNt :: (Error e, MonadError e m) => SeqData -> Pos -> m Char
    seqNt sequ (Pos off str) | off >= 0 && off < LBS.length sequ = return $ stranded str $ sequ `LBS.index` off
                             | otherwise = throwError $ strMsg $ "position " ++ show off ++ " out of SeqData bounds"

    -- | As 'seqNt', extract the nucleotide at a specific sequence
    -- position, but return @N@ when the position lies outside the
    -- bounds of the sequence.
    --
    -- > seqNtPadded sequ pos == (either 'N' id . seqNt sequ) pos
    seqNtPadded :: SeqData -> Pos -> Char
    seqNtPadded sequ (Pos off str) | off >= 0 && off < LBS.length sequ = stranded str $ sequ `LBS.index` off
                                   | otherwise = 'N'

    -- | Display a human-friendly, zero-based representation of a sequence position.
    display :: Pos -> String
    display (Pos off str) = show off ++ displayStrand
        where displayStrand = case str of
                                Fwd -> "(+)"
                                RevCompl -> "(-)"