{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ViewPatterns #-}
module Stackage.Snapshot.Diff
  ( getSnapshotDiff
  , snapshotDiff
  , SnapshotDiff()
  , toDiffList
  , toVersionedDiffList
  , VersionChange(..)
  , WithSnapshotNames(..)
  ) where

import ClassyPrelude (sortOn, toCaseFold)
import Data.Aeson
import qualified Data.Text as T (commonPrefixes)
import Data.These
import RIO
import Stackage.Database (GetStackageDatabase, SnapshotId,
                          getPackagesForSnapshotDiff)
import Types
import Web.PathPieces

data WithSnapshotNames a
    = WithSnapshotNames SnapName SnapName a

newtype SnapshotDiff
    = SnapshotDiff { toDiffList :: [(PackageNameP, VersionChange)] }
    deriving (Show, Eq, Generic, Typeable)

instance ToJSON (WithSnapshotNames SnapshotDiff) where
    toJSON (WithSnapshotNames nameA nameB (SnapshotDiff diff)) =
        object [ "comparing" .= [toPathPiece nameA, toPathPiece nameB]
               , "diff"      .= toJSON (map (second (WithSnapshotNames nameA nameB)) diff)
               ]

versionPrefix :: VersionChange -> Maybe (Text, Text, Text)
versionPrefix vc = case unVersionChange vc of
        These va vb -> T.commonPrefixes (textDisplay va) (textDisplay vb)
        _           -> Nothing

versionedDiffList ::
       [(PackageNameP, VersionChange)] -> [(PackageNameP, VersionChange, Maybe (Text, Text, Text))]
versionedDiffList = map withPrefixedVersion
  where
    withPrefixedVersion (packageName, versionChange) =
        (packageName, versionChange, versionPrefix versionChange)


toVersionedDiffList :: SnapshotDiff -> [(PackageNameP, VersionChange, Maybe (Text, Text, Text))]
toVersionedDiffList = versionedDiffList . toDiffList

-- | Versions of a package as it occurs in the listings provided to `snapshotDiff`.
--
--   Would be represented with `These v1 v2` if the package is present in both listings,
--   otherwise it would be `This v1` if the package is present only in the first listing,
--   or `That v2` if only in the second.
newtype VersionChange = VersionChange { unVersionChange :: These VersionP VersionP }
                      deriving (Show, Eq, Generic, Typeable)

instance ToJSON (WithSnapshotNames VersionChange) where
    toJSON (WithSnapshotNames (toPathPiece -> aKey) (toPathPiece -> bKey) change) =
        case change of
            VersionChange (This a)    -> object [ aKey .= a ]
            VersionChange (That b)    -> object [ bKey .= b ]
            VersionChange (These a b) -> object [ aKey .= a, bKey .= b ]

getSnapshotDiff :: GetStackageDatabase env m => SnapshotId -> SnapshotId -> m SnapshotDiff
getSnapshotDiff a b = snapshotDiff <$> getPackagesForSnapshotDiff a <*> getPackagesForSnapshotDiff b

snapshotDiff
  :: [(PackageNameP, VersionP)]
  -> [(PackageNameP, VersionP)]
  -> SnapshotDiff
snapshotDiff as0 bs0 =
    SnapshotDiff $ map (second VersionChange) $ go (sortEm as0) (sortEm bs0)
  where
    sortEm = sortOn (toCaseFold . textDisplay . fst)

    go as [] = map (second This) as
    go [] bs = map (second That) bs
    go (a:as) (b:bs) =
      case compare (fst a) (fst b) of
        EQ
          | snd a == snd b -> go as bs
          | otherwise -> (fst a, These (snd a) (snd b)) : go as bs
        LT -> (fst a, This $ snd a) : go as (b:bs)
        GT -> (fst b, That $ snd b) : go (a:as) bs
