{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

module FlatBuffers.Internal.Compiler.ParserSpec where

import           Data.Void                                ( Void )

import           FlatBuffers.Internal.Compiler.Parser
import           FlatBuffers.Internal.Compiler.SyntaxTree

import           Test.Hspec.Megaparsec

import           TestImports

import           Text.Megaparsec
import           Text.RawString.QQ                        ( r )

spec :: Spec
spec =
  describe "Parser" $ do
    describe "include" $ do
      it "parses correctly" $
        parseEof include [r|include "abc";|] `shouldParse` "abc"
      it "parses strings with semicolons" $
        parseEof include [r|include "abc;";|] `shouldParse` "abc;"
      it "parses escaped strings" $ do
        parseEof include [r|include "abc \" " ;|] `shouldParse` "abc \" "
        parseEof include [r|include "abc \" escaped \" rest" ;|] `shouldParse` "abc \" escaped \" rest"
      describe "fails to parse" $ do
        it "unmatched quotes" $
          parseEof include "include \"abc;" `shouldFailWithError` "unexpected end of input\nexpecting '\"' or literal character\n"
        it "more than one string constant" $
          parseEof include "include \"abc\" \"def\";" `shouldFailWithError` "unexpected '\"'\nexpecting ';'\n"
        it "if there's no semicolon" $
          parseEof include "include \"abc\"" `shouldFailWithError` "unexpected end of input\nexpecting ';'\n"
    describe "schema" $ do
      it "empty schema" $
        [r||] `parses` Schema [] []

      it "includes" $
        [r|
          include "somefile";
          include "other \"escaped\" File";
        |] `parses` Schema ["somefile", "other \"escaped\" File"] []

      it "includes" $
        [r|
          include "a";
          native_include "b";
          include "c";
          native_include "d";
          include "e";
        |] `parses` Schema ["a", "c", "e"] []

      it "namespaces" $
        [r|
          include "somefile";
          namespace Ns;
          namespace My . Api . Domain;
          namespace My.Api.Domain2;
          namespace ;
        |] `parses`
          Schema
            ["somefile"]
            [ DeclN "Ns"
            , DeclN "My.Api.Domain"
            , DeclN "My.Api.Domain2"
            , DeclN ""
            ]

      it "table declarations" $
        [r|
          table T {}

          table ATable {
            abc : bool;
            b1 : bool = true;
            b2 : bool = false;
            d : Ref = 123;
            d : uint;
            d : uint_;
            d : X.uint;
            d : X.uint_;
            e : [uint] = - 99.2e9 ;
            e : [uint] = 99992873786287637862.298736756627897654e99 ;
            f : [uint_];
            g : My . Api . Ref = 123;
            h : [ MyApi.abc_ ] ;
            i: Color = Blue ;
          }
        |] `parses`
          Schema
            []
            [ DeclT $ TableDecl "T" (Metadata mempty) []
            , DeclT $ TableDecl "ATable" (Metadata mempty)
              [ TableField "abc" TBool Nothing (Metadata mempty)
              , TableField "b1" TBool (Just (DefaultBool True)) (Metadata mempty)
              , TableField "b2" TBool (Just (DefaultBool False)) (Metadata mempty)
              , TableField "d" (TRef (TypeRef "" "Ref")) (Just (DefaultNum 123)) (Metadata mempty)
              , TableField "d" TWord32 Nothing (Metadata mempty)
              , TableField "d" (TRef (TypeRef "" "uint_")) Nothing (Metadata mempty)
              , TableField "d" (TRef (TypeRef "X" "uint")) Nothing (Metadata mempty)
              , TableField "d" (TRef (TypeRef "X" "uint_")) Nothing (Metadata mempty)
              , TableField "e" (TVector TWord32) (Just (DefaultNum (-99.2e9))) (Metadata mempty)
              , TableField "e" (TVector TWord32) (Just (DefaultNum 99992873786287637862.298736756627897654e99)) (Metadata mempty)
              , TableField "f" (TVector (TRef (TypeRef "" "uint_"))) Nothing (Metadata mempty)
              , TableField "g" (TRef (TypeRef "My.Api" "Ref")) (Just (DefaultNum 123)) (Metadata mempty)
              , TableField "h" (TVector (TRef (TypeRef "MyApi" "abc_"))) Nothing (Metadata mempty)
              , TableField "i" (TRef (TypeRef "" "Color")) (Just (DefaultRef "Blue")) (Metadata mempty)
              ]
            ]

      it "struct declarations" $
        [r|
          struct AStruct {
            abc : bool;
            d : Ref ;
            e : [uint] ;
            f : [uint_];
            g : My . Api . Ref ;
            h : [ MyApi.abc_ ] ;
          }
        |] `parses`
          Schema
            []
            [ DeclS $ StructDecl "AStruct" (Metadata mempty)
              [ StructField "abc" TBool (Metadata mempty)
              , StructField "d" (TRef (TypeRef "" "Ref")) (Metadata mempty)
              , StructField "e" (TVector TWord32) (Metadata mempty)
              , StructField "f" (TVector (TRef (TypeRef "" "uint_"))) (Metadata mempty)
              , StructField "g" (TRef (TypeRef "My.Api" "Ref")) (Metadata mempty)
              , StructField "h" (TVector (TRef (TypeRef "MyApi" "abc_"))) (Metadata mempty)
              ]
            ]

      it "table declarations with metadata" $
        [r|
          table ATable ( a , "b" : 9283 , c : "attr" ) {
            abc : bool = 99 ( def ) ;
          }
        |] `parses`
          Schema
            []
            [ DeclT $ TableDecl "ATable"
              (Metadata
                [ ("a", Nothing)
                , ("b", Just (AttrI 9283))
                , ("c", Just (AttrS "attr"))
                ]
              )
              (pure (TableField "abc" TBool (Just (DefaultNum 99)) (Metadata [("def", Nothing)])))
            ]

      it "enum declarations" $
        [r|
          enum Color : short (attr) {
            Red,
            Blue = 18446744073709551615,
            Gray = -18446744073709551615,
            Black
          }
        |] `parses`
          Schema
            []
            [DeclE $ EnumDecl "Color" TInt16 (Metadata [("attr", Nothing)])
              [ EnumVal "Red" Nothing
              , EnumVal "Blue" (Just 18446744073709551615)
              , EnumVal "Gray" (Just (-18446744073709551615))
              , EnumVal "Black" Nothing
              ]
            ]

      it "union declarations" $
        [r|
          union Weapon ( attr ) {
            Sword,
            mace: Stick,
            mace2: My.Api.Stick,
            Axe
          }
        |] `parses`
          Schema
            []
            [ DeclU $ UnionDecl
                "Weapon"
                (Metadata [("attr", Nothing)])
                [ UnionVal Nothing (TypeRef "" "Sword")
                , UnionVal (Just "mace") (TypeRef "" "Stick")
                , UnionVal (Just "mace2") (TypeRef "My.Api" "Stick")
                , UnionVal Nothing (TypeRef "" "Axe")
                ]
            ]

      it "root types, file extensions / identifiers, attribute declarations" $
        [r|
          attribute a;
          attribute "b";
          root_type c;
          root_type My.Api.C ;
          file_extension "d";
          file_identifier "abcd";
        |] `parses`
          Schema
            []
            [ DeclA $ AttributeDecl "a"
            , DeclA $ AttributeDecl "b"
            , DeclR $ RootDecl (TypeRef "" "c")
            , DeclR $ RootDecl (TypeRef "My.Api" "C")
            , DeclFI $ FileIdentifierDecl "abcd"
            ]

      it "file identifier must have exactly 4 UTF-8 code units" $ do
        parseEof schema [r| file_identifier "";      |] `shouldFailWithError` "file_identifier must be exactly 4 characters\n"
        parseEof schema [r| file_identifier "abc";   |] `shouldFailWithError` "file_identifier must be exactly 4 characters\n"
        parseEof schema [r| file_identifier "abcde"; |] `shouldFailWithError` "file_identifier must be exactly 4 characters\n"
        parseEof schema [r| file_identifier "abc👬"; |] `shouldFailWithError` "file_identifier must be exactly 4 UTF-8 code units\n"
        parseEof schema [r| file_identifier "a👬";   |] `shouldFailWithError` "file_identifier must be exactly 4 UTF-8 code units\n"

        [r| file_identifier "abcd"; |] `parses` Schema [] [ DeclFI "abcd" ]
        [r| file_identifier "👬";   |] `parses` Schema [] [ DeclFI "👬" ]

      it "json objects" $
        [r|
          include "a";

          {
            "a" : 3 ,
            b : "e" ,
            c : [ { d: [ [ ] , [ "a" , null , true , false , - 3 , -239.223e3 ] ] } ]
          }

          attribute b;
        |] `parses`
          Schema
            [ Include "a" ]
            [ DeclA $ AttributeDecl "b" ]

      it "RPC services" $
        [r|
          include "a";

          rpc_service MonsterStorage {
            Store(Monster) : Stat ;
            Retrieve(Stat) : Monster ( streaming : "server" , idempotent ) ;
          }

        |] `parses`
          Schema
            [ Include "a" ] []

shouldFailWithError :: Show a => Either (ParseErrorBundle String Void) a -> String -> Expectation
shouldFailWithError p s =
  case p of
    Left (ParseErrorBundle [x] _) -> parseErrorTextPretty x `shouldBe` s
    Left (ParseErrorBundle xs _)  -> expectationFailure $ "Expected one parsing error, but got more:\n" ++ show xs
    Right a                       -> expectationFailure $ "Expected parsing to fail, but succeeded with:\n" ++ show a

parseEof :: Parser a -> String -> Either (ParseErrorBundle String Void) a
parseEof p = parse (p <* eof) ""

parses :: String -> Schema -> Expectation
parses input expectedSchema =
  case parse schema "" input of
    l@(Left _) -> l `shouldParse` expectedSchema
    Right result -> result `shouldBe` expectedSchema


