{-# LANGUAGE OverloadedStrings #-}
module Data.Greskell.GraphSpec
    ( main
    , spec
    ) where

import           Control.Monad          (forM_)
import           Data.Aeson             (FromJSON, toJSON)
import qualified Data.Aeson             as Aeson
import qualified Data.ByteString.Lazy   as BSL
import           Data.HashSet           (HashSet)
import qualified Data.HashSet           as HS
import           Data.Monoid            (Monoid (..), (<>))
import           Data.Text              (Text, unpack)
import           Test.Hspec

import           Data.Greskell.AsLabel  (AsLabel (..))
import           Data.Greskell.Graph    (AEdge (..), AProperty (..), AVertex (..),
                                         AVertexProperty (..), ElementID (..), Path (..),
                                         PathEntry (..), pathToPMap)
import           Data.Greskell.GraphSON (GValueBody (..), nonTypedGValue, nonTypedGraphSON,
                                         typedGValue', typedGraphSON, typedGraphSON')
import           Data.Greskell.PMap     (lookupList, pMapFromList)

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  spec_AEdge
  spec_AProperty
  spec_AVertexProperty
  spec_AVertex
  spec_Path

loadGraphSON :: FromJSON a => FilePath -> IO (Either String a)
loadGraphSON filename = fmap Aeson.eitherDecode $ BSL.readFile ("test/graphson/" ++ filename)

spec_AEdge :: Spec
spec_AEdge = describe "AEdge" $ do
  it "should parse GraphSON v1" $ do
    let expected = nonTypedGraphSON
                   $ AEdge
                   { aeId = ElementID $ nonTypedGValue $ GNumber 13,
                     aeLabel = "develops"
                   }
    loadGraphSON "edge_v1.json" `shouldReturn` Right expected
  let expected_v23 = typedGraphSON
                     $ AEdge
                     { aeId = ElementID $ typedGValue' "g:Int32" $ GNumber 13,
                       aeLabel = "develops"
                     }
  it "should parse GraphSON v2" $ do
    loadGraphSON "edge_v2.json" `shouldReturn` Right expected_v23
  it "should parse GraphSON v3" $ do
    loadGraphSON "edge_v3.json" `shouldReturn` Right expected_v23

spec_AProperty :: Spec
spec_AProperty = describe "AProperty" $ do
  it "should parse GraphSON v1 (GValue)" $ do
    let ex = nonTypedGraphSON $ AProperty "since" $ nonTypedGValue $ GNumber 2009
    loadGraphSON "property_v1.json" `shouldReturn` Right ex
  it "should parse GraphSON v1 (bare Int)" $ do
    let ex = nonTypedGraphSON $ AProperty "since" $ (2009 :: Int)
    loadGraphSON "property_v1.json" `shouldReturn` Right ex
  let ex23 = typedGraphSON $ AProperty "since" $ typedGValue' "g:Int32" $ GNumber 2009
      ex23_nowrap = typedGraphSON $ AProperty "since" $ (2009 :: Int)
  it "should parse GraphSON v2 (GValue)" $ do
    loadGraphSON "property_v2.json" `shouldReturn` Right ex23
  it "should parse GraphSON v2 (bare Int)" $ do
    loadGraphSON "property_v2.json" `shouldReturn` Right ex23_nowrap
  it "should parse GraphSON v3 (GValue)" $ do
    loadGraphSON "property_v3.json" `shouldReturn` Right ex23
  it "should parse GraphSON v3 (bare Int)" $ do
    loadGraphSON "property_v3.json" `shouldReturn` Right ex23_nowrap

spec_AVertexProperty :: Spec
spec_AVertexProperty = describe "AVertexProperty" $ do
  it "should parse GraphSON v1" $ do
    let ex = nonTypedGraphSON $
             AVertexProperty
             { avpId = ElementID $ nonTypedGValue $ GNumber 0,
               avpLabel = "name",
               avpValue = nonTypedGValue $ GString "marko"
             }
    loadGraphSON "vertex_property_v1.json" `shouldReturn` Right ex
  let ex23 = typedGraphSON $
             AVertexProperty
             { avpId = ElementID $ typedGValue' "g:Int64" $ GNumber 0,
               avpLabel = "name",
               avpValue = nonTypedGValue $ GString "marko"
             }
  it "should parse GraphSON v2" $ do
    loadGraphSON "vertex_property_v2.json" `shouldReturn` Right ex23
  it "should parse GraphSON v3" $ do
    loadGraphSON "vertex_property_v3.json" `shouldReturn` Right ex23

spec_AVertex :: Spec
spec_AVertex = describe "AVertex" $ do
  it "should parse GraphSON v1" $ do
    let ex = nonTypedGraphSON $
             AVertex
             { avId = ElementID $ nonTypedGValue $ GNumber 1,
               avLabel = "person"
             }
    loadGraphSON "vertex_v1.json" `shouldReturn` Right ex
  let ex23 = typedGraphSON $
             AVertex
             { avId = ElementID $ typedGValue' "g:Int32" $ GNumber 1,
               avLabel = "person"
             }
  it "should parse GraphSON v2" $ do
    loadGraphSON "vertex_v2.json" `shouldReturn` Right ex23
  it "should parse GraphSON v3" $ do
    loadGraphSON "vertex_v3.json" `shouldReturn` Right ex23

mkEID :: Maybe Text -> GValueBody -> ElementID a
mkEID mtype vb =
  case mtype of
    Nothing -> ElementID $ nonTypedGValue vb
    Just t  -> ElementID $ typedGValue' t vb

mkLabels :: [Text] -> HashSet (AsLabel a)
mkLabels = HS.fromList . map AsLabel

mkPE :: [Text] -> a -> PathEntry a
mkPE labels obj = PathEntry (mkLabels labels) obj

spec_Path :: Spec
spec_Path = describe "Path" $ do
  let exp_path_v1 =
        Path
        [ PathEntry (HS.fromList ["a"]) $ AVertex (mkEID Nothing $ GNumber 1) "person",
          PathEntry (HS.fromList ["b", "c"]) $ AVertex (mkEID Nothing $ GNumber 10) "software",
          PathEntry HS.empty $ AVertex (mkEID Nothing $ GNumber 11) "software"
        ]
      exp_path_v2 =
        Path
        [ PathEntry (HS.fromList ["a"]) $ AVertex (mkEID (Just "g:Int32") $ GNumber 1) "person",
          PathEntry (HS.fromList ["b", "c"]) $ AVertex (mkEID (Just "g:Int32") $ GNumber 10) "software",
          PathEntry HS.empty $ AVertex (mkEID (Just "g:Int32") $ GNumber 11) "software"
        ]
      exp_path_v3 = exp_path_v2
  it "should parse GraphSON v1" $ do
    loadGraphSON "path_v1.json" `shouldReturn` Right exp_path_v1
  it "should parse GraphSON v2" $ do
    loadGraphSON "path_v2.json" `shouldReturn` Right exp_path_v2
  it "should parse GraphSON v3" $ do
    loadGraphSON "path_v3.json" `shouldReturn` Right exp_path_v3
  describe "pathToPMap" $ do
    specify "empty path" $ do
      let p :: Path Int
          p = mempty
      (pathToPMap p) `shouldBe` mempty
    specify "objects without labels" $ do
      let p :: Path Int
          p = Path [ PathEntry mempty 10,
                     PathEntry mempty 20,
                     PathEntry mempty 30
                   ]
      (pathToPMap p) `shouldBe` mempty
    specify "objects with unique single label" $ do
      let p :: Path Int
          p = Path
              [ mkPE ["a"] 10,
                mkPE ["b"] 20,
                mkPE [] 30,
                mkPE ["c"] 40
              ]
          expected = pMapFromList
                     [ ("a", 10), ("b", 20), ("c", 40)
                     ]
      (pathToPMap p) `shouldBe` expected
    specify "object with multiple labels" $ do
      let p :: Path Int
          p = Path
              [ mkPE ["a", "b", "c"] 10,
                mkPE ["d", "e"] 20,
                mkPE [] 30
              ]
          expected = pMapFromList
                     [ ("a", 10), ("b", 10), ("c", 10), ("d", 20), ("e", 20)
                     ]
      (pathToPMap p) `shouldBe` expected
    specify "object with shared labels" $ do
      let p :: Path Int
          p = Path
              [ mkPE ["a", "b", "c"] 10,
                mkPE ["b", "c", "d"] 20,
                mkPE ["d"] 30,
                mkPE [] 40,
                mkPE ["b"] 50
              ]
          got = pathToPMap p
          expected = pMapFromList
                     [ ("a", 10), ("b", 10), ("c", 10),
                       ("b", 20), ("c", 20), ("d", 20),
                       ("d", 30),
                       ("b", 50)
                     ]
      got `shouldBe` expected
      (lookupList ("a" :: AsLabel Int) got) `shouldBe` [10]
      (lookupList ("b" :: AsLabel Int) got) `shouldBe` [10, 20, 50]
      (lookupList ("c" :: AsLabel Int) got) `shouldBe` [10, 20]
      (lookupList ("d" :: AsLabel Int) got) `shouldBe` [20, 30]
      (lookupList ("e" :: AsLabel Int) got) `shouldBe` []
