{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}

-- | This modules provides data types that describe line-column locations
--   within a text.
module Text.Loc
  ( HasLoc(..)
  , LocLens(..)
  , Span(..)
  , SpannedLoc(..)
  , locStart
  , locEnd
  , spanOf
  , startOf
  , endOf
  , SpannedLens(..)
  , lStart
  , lEnd
  , lSpanOf
  , lEndOf
  , lStartOf
  , Located(..)
  , LineCol(..)
  , nextLine
  , nextCol
  , LineColLoc(..)
  , locLine
  , locCol
  , lineColOf
  , lineOf
  , colOf
  , LineColLens(..)
  , lLine
  , lCol
  , lLineColOf
  , lLineOf
  , lColOf
  , SrcLoc(..)
  , srcOf
  , SrcLens(..)
  , lSrcOf
  , InSrc(..)
  , FromLoc(..)
  , fromOf
  , FromLens(..)
  , lFromOf
  , Nested(..)
  ) where

import GHC.Generics (Generic)
import Data.Typeable (Typeable)
import Data.Foldable (Foldable)
import Data.Traversable (Traversable)
import Data.Hashable (Hashable(..))
import Data.Word (Word)

-- | Values with associated source locations.
class HasLoc α where
  type LocOf α
  locOf  α  LocOf α

-- | Values with editable associated source locations.
class HasLoc α  LocLens α where
  lLocOf  Functor f  (LocOf α  f (LocOf α))  α  f α

-- | A region of text between two locations.
data Span l = Span l l
              deriving (Typeable, Generic, Show, Read, Eq, Ord,
                        Functor, Foldable, Traversable)

instance Hashable l  Hashable (Span l) where
  hashWithSalt salt (Span s e) = hashWithSalt salt (s, e)
  {-# INLINE hashWithSalt #-}

instance HasLoc (Span l) where
  type LocOf (Span l) = Span l
  locOf = id
  {-# INLINE locOf #-}

instance LocLens (Span l) where
  lLocOf = id
  {-# INLINE lLocOf #-}

-- | Spanned locations.
class SpannedLoc l where
  type SpanLoc l
  locSpan  l  Span (SpanLoc l)

instance SpannedLoc (Span l) where
  type SpanLoc (Span l) = l
  locSpan = id
  {-# INLINE locSpan #-}

-- | The start of a span.
locStart  SpannedLoc l  l  SpanLoc l
locStart l = let Span s _ = locSpan l in s
{-# INLINE locStart #-}

-- | The end of a span.
locEnd  SpannedLoc l  l  SpanLoc l
locEnd l = let Span _ e = locSpan l in e
{-# INLINE locEnd #-}

-- | The span of the associated location.
spanOf  (HasLoc α, SpannedLoc (LocOf α))  α  Span (SpanLoc (LocOf α))
spanOf = locSpan . locOf
{-# INLINE spanOf #-}

-- | The start of the associated location.
startOf  (HasLoc α, SpannedLoc (LocOf α))  α  SpanLoc (LocOf α)
startOf = locStart . locOf
{-# INLINE startOf #-}

-- | The end of the associated location.
endOf  (HasLoc α, SpannedLoc (LocOf α))  α  SpanLoc (LocOf α)
endOf = locEnd . locOf
{-# INLINE endOf #-}

-- | Editable spanned locations.
class SpannedLoc l  SpannedLens l where
  lSpan  Functor f  (Span (SpanLoc l)  f (Span (SpanLoc l)))  l  f l

instance SpannedLens (Span l) where
  lSpan = id
  {-# INLINE lSpan #-}

-- | Provides access to the start of a location.
lStart  (Functor f, SpannedLens l)  (SpanLoc l  f (SpanLoc l))  l  f l
lStart f = lSpan (\(Span s e)  fmap (flip Span e) (f s))
{-# INLINE lStart #-}

-- | Provides access to the end of a location.
lEnd  (Functor f, SpannedLens l)  (SpanLoc l  f (SpanLoc l))  l  f l
lEnd f = lSpan (\(Span s e)  fmap (Span s) (f e))
{-# INLINE lEnd #-}

-- | Provides access to the span of the associated location.
lSpanOf  (Functor f, LocLens α, SpannedLens (LocOf α))
         (Span (SpanLoc (LocOf α))  f (Span (SpanLoc (LocOf α))))  α  f α
lSpanOf = lLocOf . lSpan
{-# INLINE lSpanOf #-}

-- | Provides access to the start of the associated location.
lStartOf  (Functor f, LocLens α, SpannedLens (LocOf α))
          (SpanLoc (LocOf α)  f (SpanLoc (LocOf α)))  α  f α
lStartOf = lLocOf . lStart
{-# INLINE lStartOf #-}

-- | Provides access to the end of the associated location.
lEndOf  (Functor f, LocLens α, SpannedLens (LocOf α))
        (SpanLoc (LocOf α)  f (SpanLoc (LocOf α)))  α  f α
lEndOf = lLocOf . lEnd
{-# INLINE lEndOf #-}

-- | A simple way to attach location to a value.
data Located l α = Located { locAt   l
                           , locVal  α }
                   deriving (Typeable, Generic, Show, Read,
                             Functor, Foldable, Traversable)

instance (Hashable l, Hashable α)  Hashable (Located l α) where
  hashWithSalt salt (Located l a) = hashWithSalt salt (l, a)
  {-# INLINE hashWithSalt #-}

instance HasLoc (Located l α) where
  type LocOf (Located l α) = l
  locOf = locAt
  {-# INLINE locOf #-}

instance LocLens (Located l α) where
  lLocOf f (Located l a) = fmap (flip Located a) (f l)
  {-# INLINE lLocOf #-}

-- | Line-column location within a text.
data LineCol = LineCol {-# UNPACK #-} !Word {-# UNPACK #-} !Word
               deriving (Typeable, Generic, Show, Read, Eq, Ord, Bounded)

instance Hashable LineCol where
  hashWithSalt salt (LineCol l c) = hashWithSalt salt (l, c)
  {-# INLINE hashWithSalt #-}

instance HasLoc LineCol where
  type LocOf LineCol = LineCol
  locOf = id
  {-# INLINE locOf #-}

-- | The location of the start of the next line.
nextLine  LineCol  LineCol
nextLine (LineCol l _) = LineCol (l + 1) 1
{-# INLINE nextLine #-}

-- | The location of the next column.
nextCol  LineCol  LineCol
nextCol (LineCol l c) = LineCol l (c + 1)
{-# INLINE nextCol #-}

-- | Locations with line and column numbers.
class LineColLoc l where
  locLineCol  l  LineCol

instance LineColLoc LineCol where
  locLineCol = id
  {-# INLINE locLineCol #-}

-- | The line number of a location.
locLine  LineColLoc l  l  Word
locLine l = let LineCol ln _ = locLineCol l in ln
{-# INLINE locLine #-}

-- | The column number of a location.
locCol  LineColLoc l  l  Word
locCol l = let LineCol _ c = locLineCol l in c
{-# INLINE locCol #-}

-- | The line-column numbers of the associated location.
lineColOf  (HasLoc α, LineColLoc (LocOf α))  α  LineCol
lineColOf = locLineCol . locOf
{-# INLINE lineColOf #-}

-- | The line number of the associated location.
lineOf  (HasLoc α, LineColLoc (LocOf α))  α  Word
lineOf = locLine . lineColOf
{-# INLINE lineOf #-}

-- | The column number of the associated location.
colOf  (HasLoc α, LineColLoc (LocOf α))  α  Word
colOf = locCol . lineColOf
{-# INLINE colOf #-}

-- | Locations with editable line and column numbers.
class LineColLens l where
  lLineCol  Functor f  (LineCol  f LineCol)  l  f l

instance LineColLens LineCol where
  lLineCol = id
  {-# INLINE lLineCol #-}

-- | Provides access to the line number of a location.
lLine  (Functor f, LineColLens l)  (Word  f Word)  l  f l
lLine f = lLineCol (\(LineCol l c)  fmap (flip LineCol c) (f l))
{-# INLINE lLine #-}

-- | Provides access to the column number of a location.
lCol  (Functor f, LineColLens l)  (Word  f Word)  l  f l
lCol f = lLineCol (\(LineCol l c)  fmap (LineCol l) (f c))
{-# INLINE lCol #-}

-- | Provides access to the line-column numbers of the associated location.
lLineColOf  (Functor f, LocLens α, LineColLens (LocOf α))
            (LineCol  f LineCol)  α  f α
lLineColOf = lLocOf . lLineCol
{-# INLINE lLineColOf #-}

-- | Provides access to the line number of the associated location.
lLineOf  (Functor f, LocLens α, LineColLens (LocOf α))
         (Word  f Word)  α  f α
lLineOf = lLocOf . lLine
{-# INLINE lLineOf #-}

-- | Provides access to the column number of the associated location.
lColOf  (Functor f, LocLens α, LineColLens (LocOf α))
        (Word  f Word)  α  f α
lColOf = lLocOf . lCol
{-# INLINE lColOf #-}

-- | Locations with a source (e.g. file name).
class SrcLoc l where
  type LocSrc l
  locSrc  l  LocSrc l

-- | The source of the associated location.
srcOf  (HasLoc α, SrcLoc (LocOf α))  α  LocSrc (LocOf α)
srcOf = locSrc . locOf
{-# INLINE srcOf #-}

-- | Locations with editable source (e.g. file name).
class SrcLoc l  SrcLens l where
  lSrc  Functor f  (LocSrc l  f (LocSrc l))  l  f l

-- | Provides access to the source of the associated location.
lSrcOf  (Functor f, LocLens α, SrcLens (LocOf α))
        (LocSrc (LocOf α)  f (LocSrc (LocOf α)))  α  f α
lSrcOf = lLocOf . lSrc
{-# INLINE lSrcOf #-}

-- | A simple way to add source to a location.
data InSrc s l = InSrc { srcSrc  s
                       , srcLoc  l }
                 deriving (Typeable, Generic, Show, Read, Eq, Ord)

instance (Hashable s, Hashable l)  Hashable (InSrc s l) where
  hashWithSalt salt (InSrc s l) = hashWithSalt salt (s, l)
  {-# INLINE hashWithSalt #-}

instance HasLoc (InSrc s l) where
  type LocOf (InSrc s l) = InSrc s l
  locOf = id
  {-# INLINE locOf #-}

instance LocLens (InSrc s l) where
  lLocOf = id
  {-# INLINE lLocOf #-}

instance SpannedLoc l  SpannedLoc (InSrc s l) where
  type SpanLoc (InSrc s l) = InSrc s (SpanLoc l)
  locSpan (InSrc s l) = fmap (InSrc s) (locSpan l)
  {-# INLINE locSpan #-}

instance SpannedLens l  SpannedLens (InSrc s l) where
  lSpan f (InSrc s l) =
    fmap (InSrc s) (lSpan (fmap (fmap srcLoc) . f . fmap (InSrc s)) l)
  {-# INLINE lSpan #-}

instance LineColLoc l  LineColLoc (InSrc s l) where
  locLineCol = locLineCol . srcLoc
  {-# INLINE locLineCol #-}

instance LineColLens l  LineColLens (InSrc s l) where
  lLineCol f (InSrc s l) = fmap (InSrc s) (lLineCol f l)
  {-# INLINE lLineCol #-}

instance SrcLoc (InSrc s l) where
  type LocSrc (InSrc s l) = s
  locSrc = srcSrc
  {-# INLINE locSrc #-}

instance SrcLens (InSrc s l) where
  lSrc f (InSrc s l) = fmap (flip InSrc l) (f s)
  {-# INLINE lSrc #-}

-- | Locations that have parent locations.
class FromLoc l where
  type LocFrom l
  locFrom  l  LocFrom l

instance FromLoc l  FromLoc (InSrc s l) where
  type LocFrom (InSrc s l) = LocFrom l
  locFrom = locFrom . srcLoc
  {-# INLINE locFrom #-}

instance FromLens l  FromLens (InSrc s l) where
  lFrom f (InSrc s l) = fmap (InSrc s) (lFrom f l)
  {-# INLINE lFrom #-}

-- | The parent of the associated location.
fromOf  (HasLoc α, FromLoc (LocOf α))  α  LocFrom (LocOf α)
fromOf = locFrom . locOf
{-# INLINE fromOf #-}

-- | Locations with editable parent locations.
class FromLoc l  FromLens l where
  lFrom  Functor f  (LocFrom l  f (LocFrom l))  l  f l

-- | Provides access to the parent of the associated location.
lFromOf  (Functor f, LocLens α, FromLens (LocOf α))
         (LocFrom (LocOf α)  f (LocFrom (LocOf α)))  α  f α
lFromOf = lLocOf . lFrom
{-# INLINE lFromOf #-}

-- | A simple way to nest a location.
data Nested l p = Nested { nestedLoc   l
                         , nestedFrom  p }
                  deriving (Typeable, Generic, Show, Read)

instance (Hashable l, Hashable p)  Hashable (Nested l p) where
  hashWithSalt salt (Nested l p) = hashWithSalt salt (l, p)
  {-# INLINE hashWithSalt #-}

instance HasLoc (Nested l p) where
  type LocOf (Nested l p) = Nested l p
  locOf = id
  {-# INLINE locOf #-}

instance LocLens (Nested l p) where
  lLocOf = id
  {-# INLINE lLocOf #-}

instance SpannedLoc l  SpannedLoc (Nested l p) where
  type SpanLoc (Nested l p) = Nested (SpanLoc l) p
  locSpan (Nested l p) = fmap (flip Nested p) (locSpan l)
  {-# INLINE locSpan #-}

instance SpannedLens l  SpannedLens (Nested l p) where
  lSpan f (Nested l p) =
    fmap (flip Nested p)
         (lSpan (fmap (fmap nestedLoc) . f . fmap (flip Nested p)) l)
  {-# INLINE lSpan #-}

instance LineColLoc l  LineColLoc (Nested l p) where
  locLineCol = locLineCol . nestedLoc
  {-# INLINE locLineCol #-}

instance LineColLens l  LineColLens (Nested l p) where
  lLineCol f (Nested l p) = fmap (flip Nested p) (lLineCol f l)
  {-# INLINE lLineCol #-}

instance SrcLoc l  SrcLoc (Nested l p) where
  type LocSrc (Nested l p) = LocSrc l
  locSrc = locSrc . nestedLoc
  {-# INLINE locSrc #-}

instance SrcLens l  SrcLens (Nested l p) where
  lSrc f (Nested l p) = fmap (flip Nested p) (lSrc f l)
  {-# INLINE lSrc #-}

instance FromLoc (Nested l p) where
  type LocFrom (Nested l p) = p
  locFrom = nestedFrom
  {-# INLINE locFrom #-}

instance FromLens (Nested l p) where
  lFrom f (Nested l p) = fmap (Nested l) (f p)
  {-# INLINE lFrom #-}