content: {- Copyright 2013-2021 NGLess Authors
   - License: MIT
   -}
  {-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
  -- | Unit tests are their own programme.
  --
  -- Unit tests written in Haskell have less overhead than full integration tests
  -- in the tests/ directory, but are not always as convenient.
  
  module Main where
  
  import Test.Tasty
  import Test.Tasty.TH
  import Test.Tasty.HUnit
  import Text.Parsec (parse)
  import Text.Parsec.Combinator (eof)
  
  import System.Directory (removeDirectoryRecursive)
  import qualified Data.Vector.Storable as VS
  import qualified Data.ByteString.Char8 as B
  
  import qualified Data.Conduit as C
  import           Data.Conduit ((.|))
  import           Control.Monad.State.Strict (execState, modify')
  import           Data.Convertible (convert)
  import           Data.Conduit.Algorithms.Async (conduitPossiblyCompressedFile)
  
  import Language
  import Interpret
  import Tokens
  import FileManagement
  import NGLess
  import NGLess.NGLEnvironment (setupTestEnvironment)
  
  import Interpretation.Unique
  
  import Data.Sam
  import Data.FastQ
  import Utils.Conduit
  import Utils.Samtools (samBamConduit)
  import Utils.Here
  import qualified Data.GFF as GFF
  
  import Tests.Utils
  import Tests.Count (tgroup_Count)
  import Tests.FastQ (tgroup_FastQ)
  import Tests.IntGroups (tgroup_IntGroups)
  import Tests.Language (tgroup_Language)
  import Tests.LoadFQDirectory (tgroup_LoadFQDirectory)
  import Tests.NGLessAPI (tgroup_NGLessAPI)
  import Tests.Parse (tgroup_Parse)
  import Tests.Samples (tgroup_Samples)
  import Tests.Select (tgroup_Select)
  import Tests.Types (tgroup_Types)
  import Tests.Validation (tgroup_Validation)
  import Tests.Vector (tgroup_Vector)
  import Tests.Write (tgroup_Write)
  
  test_FastQ      = [tgroup_FastQ]
  test_Validation = [tgroup_Validation]
  test_Count      = [tgroup_Count]
  test_Parse      = [tgroup_Parse]
  test_Types      = [tgroup_Types]
  test_NGLessAPI  = [tgroup_NGLessAPI]
  test_Vector     = [tgroup_Vector]
  test_IntGroups  = [tgroup_IntGroups]
  test_Samples    = [tgroup_Samples]
  test_Select     = [tgroup_Select]
  test_Language   = [tgroup_Language]
  test_LoadFqDir  = [tgroup_LoadFQDirectory]
  test_Write      = [tgroup_Write]
  
  -- The main test driver sets up the config options then uses the automatically
  -- generated function
  main = do
      setupTestEnvironment
      $(defaultMainGenerator)
      removeDirectoryRecursive "testing_directory_tmp"
  
  -- Test Tokens module
  tokenize' fn t = map snd <$> (tokenize fn t)
  
  case_tok_cr = TNewLine @=? (case parse (Tokens.eol <* eof) "test" "\r\n" of { Right t -> t; Left _ -> error "Parse failed"; })
  case_tok_single_line_comment = tokenize' "test" with_comment @?= Right expected
      where
          with_comment = "a=0# comment\nb=1\n"
          expected = [TWord "a",TOperator '=',TExpr (ConstInt 0),TNewLine,TWord "b",TOperator '=',TExpr (ConstInt 1),TNewLine]
  
  case_tok_single_line_comment_cstyle = tokenize' "test" with_comment @?= Right expected
      where
          with_comment = "a=0// comment\nb=1\n"
          expected = [TWord "a",TOperator '=',TExpr (ConstInt 0),TNewLine,TWord "b",TOperator '=',TExpr (ConstInt 1),TNewLine]
  
  case_tok_multi_line_comment = tokenize' "test" with_comment @?= Right expected
      where
          with_comment = "a=0/* This\n\nwith\nlines*/\nb=1\n"
          expected = [TWord "a",TOperator '=',TExpr (ConstInt 0),TIndent 0,TNewLine,TWord "b",TOperator '=',TExpr (ConstInt 1),TNewLine]
  
  case_tok_word_ = tokenize' "test" "word_with_underscore" @?= Right expected
      where
          expected = [TWord "word_with_underscore"]
  
  
  
  -- Test Types
  case_indent_comment = isOk "ParseFailed" $ parsetest indent_comment
  case_indent_space = isOk "ParseFailed" $ parsetest indent_space
  
  indent_comment = "ngless '0.0'\n\
      \reads = fastq('input1.fq')\n\
      \reads = preprocess(reads) using |read|:\n\
      \    read = read[5:]\n\
      \    # comment \n"
  
  indent_space  = "ngless '0.0'\n\
      \reads = fastq('input1.fq')\n\
      \reads = preprocess(reads) using |read|:\n\
      \    read = read[5:]\n\
      \    \n"
  
  
  -- Type Validate pre process operations
  sr i s q = NGOShortRead (ShortRead i s $ VS.generate (B.length q) (convert . B.index q))
  
  case_pre_process_indexation_1 = _evalIndex' (sr "@IRIS" "AGTACCAA" "aa`aaaaa") [Just (NGOInteger 5), Nothing] @?= (sr "@IRIS" "CAA" "aaa")
  case_pre_process_indexation_2 = _evalIndex' (sr "@IRIS" "AGTACCAA" "aa`aaaaa") [Nothing, Just (NGOInteger 3)] @?= (sr "@IRIS" "AGT" "aa`")
  case_pre_process_indexation_3 = _evalIndex' (sr "@IRIS" "AGTACCAA" "aa`aaaaa") [Just (NGOInteger 2), Just (NGOInteger 5)] @?= (sr "@IRIS" "TAC" "`aa")
  
  _evalIndex' a b = case _evalIndex a b of
      Right v -> v
      Left err -> error (show err)
  
  case_pre_process_length_1 = _evalUnary UOpLen (sr "@IRIS" "AGTACCAA" "aa`aaaaa") @?= Right (NGOInteger 8)
  
  case_bop_gte_1 = evalBinary BOpGTE (NGOInteger 10) (NGOInteger 10) @?= Right (NGOBool True)
  case_bop_gte_2 = evalBinary BOpGTE (NGOInteger 11) (NGOInteger 10) @?= Right (NGOBool True)
  case_bop_gte_3 = evalBinary BOpGTE (NGOInteger 10) (NGOInteger 11) @?= Right (NGOBool False)
  
  case_bop_gt_1 = evalBinary BOpGT (NGOInteger 10) (NGOInteger 10) @?= Right (NGOBool False)
  case_bop_gt_2 = evalBinary BOpGT (NGOInteger 11) (NGOInteger 10) @?= Right (NGOBool True)
  case_bop_gt_3 = evalBinary BOpGT (NGOInteger 10) (NGOInteger 11) @?= Right (NGOBool False)
  
  case_bop_lt_1 = evalBinary BOpLT (NGOInteger 10) (NGOInteger 10) @?= Right (NGOBool False)
  case_bop_lt_2 = evalBinary BOpLT (NGOInteger 11) (NGOInteger 10) @?= Right (NGOBool False)
  case_bop_lt_3 = evalBinary BOpLT (NGOInteger 10) (NGOInteger 11) @?= Right (NGOBool True)
  
  case_bop_lte_1 = evalBinary BOpLTE (NGOInteger 10) (NGOInteger 10) @?= Right (NGOBool True)
  case_bop_lte_2 = evalBinary BOpLTE (NGOInteger 11) (NGOInteger 10) @?= Right (NGOBool False)
  case_bop_lte_3 = evalBinary BOpLTE (NGOInteger 10) (NGOInteger 11) @?= Right (NGOBool True)
  
  case_bop_eq_1 = evalBinary BOpEQ (NGOInteger 10) (NGOInteger 10) @?= Right (NGOBool True)
  case_bop_eq_2 = evalBinary BOpEQ (NGOInteger 10) (NGOInteger 0) @?= Right (NGOBool False)
  
  case_bop_neq_1 = evalBinary BOpNEQ (NGOInteger 0) (NGOInteger 10) @?= Right (NGOBool True)
  case_bop_neq_2 = evalBinary BOpNEQ (NGOInteger 10) (NGOInteger 10) @?= Right (NGOBool False)
  
  case_bop_add_1 = evalBinary BOpAdd (NGOInteger 0) (NGOInteger 10) @?= Right (NGOInteger 10)
  case_bop_add_2 = evalBinary BOpAdd (NGOInteger 10) (NGOInteger 0) @?= Right (NGOInteger 10)
  case_bop_add_3 = evalBinary BOpAdd (NGOInteger 10) (NGOInteger 10) @?= Right (NGOInteger 20)
  
  case_bop_mul_1 = evalBinary BOpMul (NGOInteger 0) (NGOInteger 10) @?= Right (NGOInteger 0)
  case_bop_mul_2 = evalBinary BOpMul (NGOInteger 10) (NGOInteger 0) @?= Right (NGOInteger 0)
  case_bop_mul_3 = evalBinary BOpMul (NGOInteger 10) (NGOInteger 10) @?= Right (NGOInteger 100)
  
  case_bop_add_path_1 = evalBinary BOpPathAppend (NGOString "dir") (NGOString "file") @?= Right (NGOString "dir/file")
  case_bop_add_path_2 = evalBinary BOpPathAppend (NGOString "dir/subdir") (NGOString "file") @?= Right (NGOString "dir/subdir/file")
  case_bop_add_path_3 = evalBinary BOpPathAppend (NGOString "dir/subdir/") (NGOString "file") @?= Right (NGOString "dir/subdir/file")
  case_bop_add_path_4 = evalBinary BOpPathAppend (NGOString "../dir/subdir/") (NGOString "file") @?= Right (NGOString "../dir/subdir/file")
  case_bop_add_path_5 = evalBinary BOpPathAppend (NGOString "/abs/dir/subdir/") (NGOString "file") @?= Right (NGOString "/abs/dir/subdir/file")
  
  case_uop_minus_1 = _evalUnary UOpMinus (NGOInteger 10) @?= Right (NGOInteger (-10))
  case_uop_minus_2 = _evalUnary UOpMinus (NGOInteger (-10)) @?= Right (NGOInteger 10)
  
  --
  
  case_template_id = takeBaseNameNoExtensions "a/B/c/d/xpto_1.fq" @?= takeBaseNameNoExtensions "a/B/c/d/xpto_1.fq"
  case_template    = takeBaseNameNoExtensions "a/B/c/d/xpto_1.fq" @?= "xpto_1"
  
  samStats :: FilePath -> NGLessIO (Int, Int, Int)
  samStats fname = C.runConduit (samBamConduit fname .| linesVC 1024 .| samStatsC) >>= runNGLess
  
  case_sam20 = do
          sam <- testNGLessIO $ asTempFile sam20 "sam"  >>= samStats
          sam @?= (5,0,0)
      where
          sam20 = [here|
  @SQ	SN:I	LN:230218
  @PG	ID:bwa	PN:bwa	VN:0.7.7-r441	CL:/home/luispedro/.local/share/ngless/bin/ngless-0.0.0-bwa mem -t 1 /home/luispedro/.local/share/ngless/data/sacCer3/Sequence/BWAIndex/reference.fa.gz /tmp/preprocessed_sample20.fq1804289383846930886.gz
  IRIS:7:1:17:394#0	4	*	0	0	*	*	0	0	GTCAGGACAAGAAAGACAANTCCAATTNACATT	aaabaa`]baaaaa_aab]D^^`b`aYDW]aba	AS:i:0	XS:i:0
  IRIS:7:1:17:800#0	4	*	0	0	*	*	0	0	GGAAACACTACTTAGGCTTATAAGATCNGGTTGCGG	ababbaaabaaaaa`]`ba`]`aaaaYD\\_a``XT	AS:i:0	XS:i:0
  IRIS:7:1:17:1757#0	4	*	0	0	*	*	0	0	TTTTCTCGACGATTTCCACTCCTGGTCNAC	aaaaaa``aaa`aaaa_^a```]][Z[DY^	AS:i:0	XS:i:0
  IRIS:7:1:17:1479#0	4	*	0	0	*	*	0	0	CATATTGTAGGGTGGATCTCGAAAGATATGAAAGAT	abaaaaa`a```^aaaaa`_]aaa`aaa__a_X]``	AS:i:0	XS:i:0
  IRIS:7:1:17:150#0	4	*	0	0	*	*	0	0	TGATGTACTATGCATATGAACTTGTATGCAAAGTGG	abaabaa`aaaaaaa^ba_]]aaa^aaaaa_^][aa	AS:i:0	XS:i:0
  |]
  -- Parse GFF lines
  
  
  
  case_trim_attrs_1  = GFF._trimString " x = 10" @?= "x = 10"
  case_trim_attrs_2  = GFF._trimString " x = 10 " @?= "x = 10"
  case_trim_attrs_3  = GFF._trimString "x = 10 " @?= "x = 10"
  case_trim_attrs_4  = GFF._trimString "x = 10" @?= "x = 10"
  case_trim_attrs_5  = GFF._trimString "   X    " @?= "X"
  
  
  case_parse_gff_line = GFF.readGffLine gff_line @?= Right gff_structure
      where
          gff_line = "chrI\tunknown\texon\t4124\t4358\t.\t-\t.\tgene_id \"Y74C9A.3\"; transcript_id \"NM_058260\"; gene_name \"Y74C9A.3\"; p_id \"P23728\"; tss_id \"TSS14501\";"
          gff_structure  = GFF.GffLine "chrI" "unknown" "exon" 4124 4358 Nothing GFF.GffNegStrand (-1) attrsExpected
          attrsExpected = [("gene_id","Y74C9A.3"), ("transcript_id" ,"NM_058260"), ("gene_name", "Y74C9A.3"), ("p_id", "P23728"), ("tss_id", "TSS14501")]
  
  -- _parseGffAttributes
  case_parse_gff_atributes_normal_1 = GFF._parseGffAttributes "ID=chrI;dbxref=NCBI:NC_001133;Name=chrI" @?= [("ID","chrI"),("dbxref","NCBI:NC_001133"),("Name","chrI")]
  case_parse_gff_atributes_normal_2 = GFF._parseGffAttributes "gene_id=chrI;dbxref=NCBI:NC_001133;Name=chrI" @?= [("gene_id","chrI"),("dbxref","NCBI:NC_001133"),("Name","chrI")]
  case_parse_gff_atributes_trail_del = GFF._parseGffAttributes "gene_id=chrI;dbxref=NCBI:NC_001133;Name=chrI;" @?= [("gene_id","chrI"),("dbxref","NCBI:NC_001133"),("Name","chrI")]
  case_parse_gff_atributes_trail_del_space = GFF._parseGffAttributes "gene_id=chrI;dbxref=NCBI:NC_001133;Name=chrI; " @?= [("gene_id","chrI"),("dbxref","NCBI:NC_001133"),("Name","chrI")]
  
  
  case_calc_sam_stats = testNGLessIO (samStats "test_samples/sample.sam.gz") >>= \r ->
    r @?=  (2772,1310,1299)
  
  --- Unique.hs
  
  --File "test_samples/data_set_repeated.fq" has 216 reads in which 54 are unique.
  
  countC = loop (0 :: Int)
      where
          loop !n = C.await >>= maybe (return n) (const (loop $ n+1))
  make_unique_test n = let enc = SolexaEncoding in do
      nuniq <- testNGLessIO $ do
          newfp <- performUnique "test_samples/data_set_repeated.fq" enc n
          C.runConduit $
              conduitPossiblyCompressedFile newfp
                  .| linesC
                  .| fqDecodeC "testing" enc
                  .| countC
      let n' = min n 4
      nuniq @?=  (n' * 54)
  
  case_unique_1 = make_unique_test 1
  case_unique_2 = make_unique_test 2
  case_unique_3 = make_unique_test 3
  case_unique_4 = make_unique_test 4
  case_unique_5 = make_unique_test 5
  
  
  case_recursiveAnalyze = execState (recursiveAnalyse countFcalls expr) 0 @?= (1 :: Int)
      where
          countFcalls (FunctionCall  _ _ _ _) = modify' (+1)
          countFcalls _ = return ()
  
          expr = Assignment
                      (Variable "varname")
                      (FunctionCall (FuncName "count")
                          (Lookup Nothing (Variable "mapped"))
                          [(Variable "features", ListExpression [ConstStr "seqname"])
                              ,(Variable "multiple", ConstSymbol "all1")]
                          Nothing)
  
  
  case_expand_path = do
      expandPath' "/nothing1/file.txt" [] @?=  ["/nothing1/file.txt"]
      expandPath' "/nothing2/file.txt" [undefined] @?=  ["/nothing2/file.txt"]
      expandPath' "/nothing3/file.txt" ["/home/luispedro/my-directory"] @?=  ["/nothing3/file.txt"]
      expandPath' "<>/nothing4/file.txt" ["/home/luispedro/my-directory1"] @?=  ["/home/luispedro/my-directory1/nothing4/file.txt"]
      expandPath' "<>/nothing4/file.txt" ["refs=/home/luispedro/my-directory1"] @?=  []
      expandPath' "<>/nothing/file.txt" ["/home/luispedro/my-directory"
                                        ,"/home/alternative/your-directory"] @?=  ["/home/luispedro/my-directory/nothing/file.txt"
                                                                                  ,"/home/alternative/your-directory/nothing/file.txt"]
      expandPath' "<refs>/nothing/file.txt" ["/home/luispedro/my-directory"
                                        ,"/home/alternative/your-directory"] @?=  ["/home/luispedro/my-directory/nothing/file.txt"
                                                                                  ,"/home/alternative/your-directory/nothing/file.txt"]
      expandPath' "<refs>/nothing/file.txt" ["refs=/home/luispedro/my-directory"
                                        ,"/home/alternative/your-directory"] @?=  ["/home/luispedro/my-directory/nothing/file.txt"
                                                                                  ,"/home/alternative/your-directory/nothing/file.txt"]
      expandPath' "<refs>/nothing/file.txt" ["refs=/home/luispedro/my-directory"
                                        ,"nope=/home/alternative/your-directory"] @?=  ["/home/luispedro/my-directory/nothing/file.txt"]
      expandPath' "<refs>/nothing/file.txt" ["other=/home/luispedro/my-directory"
                                        ,"nope=/home/alternative/your-directory"] @?=  []
      expandPath' "<refs>/nothing/file.txt" [] @?= []
  