/*
 * Copyright 2013-2026, Seqera Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nextflow

import java.nio.file.Files
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.TimeUnit

import nextflow.file.FileHelper
import org.junit.Rule
import spock.lang.Specification
import spock.lang.Timeout
import spock.lang.Unroll
import test.TemporaryPath
import test.TestHelper

import static test.ScriptHelper.runDataflow
/**
 *
 * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
 */
@Timeout(value = 1, unit = TimeUnit.MINUTES)
class ChannelTest extends Specification {

    def 'should create a channel of values' () {
        when:
        def result = runDataflow {
            Channel.of('a')
        }
        then:
        result.val == 'a'
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of('a','b','c')
        }
        then:
        result.val == 'a'
        result.val == 'b'
        result.val == 'c'
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of([1,2,3])
        }
        then:
        result.val == [1,2,3]
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of([1,2], [3,4])
        }
        then:
        result.val == [1,2]
        result.val == [3,4]
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of([])
        }
        then:
        result.val == []
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of()
        }
        then:
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of([1,2,3].toArray())
        }
        then:
        result.val == 1
        result.val == 2
        result.val == 3
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of([].toArray())
        }
        then:
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of(null)
        }
        then:
        result.val == null
        result.val == Channel.STOP

    }

    def 'should create a channel from a range' () {
        when:
        def result = runDataflow {
            Channel.of(1..3)
        }
        then:
        result.val == 1
        result.val == 2
        result.val == 3
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of(1..3,'X','Y')
        }
        then:
        result.val == 1
        result.val == 2
        result.val == 3
        result.val == 'X'
        result.val == 'Y'
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.of(1..3,'X'..'Y')
        }
        then:
        result.val == 1
        result.val == 2
        result.val == 3
        result.val == 'X'
        result.val == 'Y'
        result.val == Channel.STOP
    }

    def 'should create channel from a list'() {
        when:
        def result = runDataflow {
            Channel.fromList(['alpha','delta'])
        }
        then:
        result.val == 'alpha'
        result.val == 'delta'
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.fromList([])
        }
        then:
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.fromList(null)
        }
        then:
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.fromList([1..3, 'X'..'Y'])
        }
        then:
        result.val == 1..3
        result.val == 'X'..'Y'
        result.val == Channel.STOP
    }

    def testFrom() {
        when:
        def result = runDataflow {
            Channel.from('hola')
        }
        then:
        result.val == 'hola'
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.from('alpha','delta')
        }
        then:
        result.val == 'alpha'
        result.val == 'delta'
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.from(['alpha','delta'])
        }
        then:
        result.val == 'alpha'
        result.val == 'delta'
        result.val == Channel.STOP

        when:
        result = runDataflow {
            Channel.from([])
        }
        then:
        result.val == Channel.STOP
    }

    def testSingleFile() {

        when:
        def channel = runDataflow {
            Channel.fromPath('/some/file.txt')
        }
        then:
        channel.val == Paths.get('/some/file.txt')

        when:
        channel = runDataflow {
            Channel.fromPath('/some/f{i}le.txt')
        }
        then:
        channel.val == Paths.get('/some/f{i}le.txt')

    }

    @Rule
    TemporaryPath tempDir = new TemporaryPath()

    @Unroll
    def testGlobAlternative() {

        given:
        def folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('alpha.txt'))
        def file2 = Files.createFile(folder.resolve('beta.txt'))
        def file3 = Files.createFile(folder.resolve('gamma.txt'))
        def file4 = Files.createFile(folder.resolve('file4.txt'))
        def file5 = Files.createFile(folder.resolve('file5.txt'))
        def file6 = Files.createFile(folder.resolve('file66.txt'))
        and:
        def result = runDataflow {
            Channel.fromPath("$folder/$PATTERN")
                .map { path -> path.name }
                .toSortedList()
        }

        expect:
        result.val == RESULT

        where:
        PATTERN             | RESULT
        "{alpha,gamma}.txt" | [ 'alpha.txt', 'gamma.txt' ]
        "file?.txt"         | [ 'file4.txt', 'file5.txt' ]
        "file*.txt"         | [ 'file4.txt', 'file5.txt', 'file66.txt' ]
        "{alpha,}.txt"      | ['alpha.txt']
        "{,beta}.txt"       | ['beta.txt']
        "alpha.txt{,}"      | ['alpha.txt']
    }

    def testRelative() {

        setup:
        def file1 = new File('alpha.txt')

        when:
        def result = runDataflow {
            Channel.fromPath([relative:false], 'alpha.txt').toSortedList()
        }
        then:
        result.val*.toString() == [ file1.absolutePath ]

        when:
        result = runDataflow {
            Channel.fromPath([relative:true], 'alpha.txt').toSortedList()
        }
        then:
        result.val*.toString() == [ 'alpha.txt' ]

        when: 'no relative option set'
        result = runDataflow {
            Channel.fromPath([:], 'alpha.txt').toSortedList()
        }
        then:
        result.val*.toString() == [ file1.absolutePath ]

        cleanup:
        file1.delete()
    }

    def testGlobHiddenFiles() {

        setup:
        final folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('.alpha.txt'))
        def file2 = Files.createFile(folder.resolve('.beta.txt'))
        def file3 = Files.createFile(folder.resolve('delta.txt'))
        def file4 = Files.createFile(folder.resolve('gamma.txt'))

        when:
        def result = runDataflow {
            Channel.fromPath("$folder/*").toSortedList()
        }
        then:
        result.val == [file3, file4]

        when:
        result = runDataflow {
            Channel.fromPath("$folder/.*").toSortedList()
        }
        then:
        result.val == [file1, file2]

        when:
        result = runDataflow {
            Channel.fromPath("$folder/{.*,*}", hidden: true).toSortedList()
        }
        then:
        result.val == [file1, file2, file3, file4]

    }

    def testGlobFiles() {

        setup:
        def folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('file1.txt'))
        def file2 = Files.createFile(folder.resolve('file2.txt'))
        def file3 = Files.createFile(folder.resolve('file3.txt'))
        def file4 = Files.createFile(folder.resolve('file4.log'))
        def sub1 = Files.createDirectories(folder.resolve('sub1'))
        def file5 = Files.createFile(sub1.resolve('file5.log'))
        def file6 = Files.createFile(sub1.resolve('file6.txt'))

        when:
        def result = runDataflow {
            Channel.fromPath("$folder/*.txt").toSortedList()
        }
        then:
        result.val == [file1, file2, file3]

        when:
        result = runDataflow {
            Channel.fromPath("$folder/**.txt").toSortedList()
        }
        then:
        result.val == [file1, file2, file3, file6]

        when:
        result = runDataflow {
            Channel.fromPath("$folder/sub1/**.log").toSortedList()
        }
        then:
        result.val == [file5]

    }

    def testEscapeGlob() {

        setup:
        def folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('file1.txt'))
        def file2 = Files.createFile(folder.resolve('file*.txt'))
        def file3 = Files.createFile(folder.resolve('file?.txt'))
        def sub1 = Files.createDirectories(folder.resolve('sub[a-b]'))
        def file5 = Files.createFile(sub1.resolve('file5.log'))
        def file6 = Files.createFile(sub1.resolve('file6.txt'))

        when:
        def result = runDataflow {
            Channel.fromPath("$folder/file\\*.txt").toSortedList()
        }
        then:
        result.val == [file2]

        when:
        result = runDataflow {
            Channel.fromPath("$folder/file*.txt", glob: false).toSortedList()
        }
        then:
        result.val == [file2]

        when:
        result = runDataflow {
            Channel.fromPath("$folder/sub\\[a-b\\]/file*").toSortedList()
        }
        then:
        result.val == [file5,file6]

    }


    @Unroll
    def testFromPath() {

        setup:
        def folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('file1.txt'))
        def file2 = Files.createFile(folder.resolve('file2.txt'))
        def file3 = Files.createFile(folder.resolve('file3.log'))
        def sub1 = Files.createDirectories(folder.resolve('sub1'))
        def file5 = Files.createFile(sub1.resolve('file5.log'))

        def result = runDataflow {
            Channel.fromPath( OPTS, folder.toAbsolutePath().toString() + '/' + PATTERN )
                .map { path -> path.name }
                .toSortedList()
        }

        expect:
        result.val == RESULT

        where:
        PATTERN | OPTS                          | RESULT
        '*.txt' | [:]                           | [ 'file1.txt', 'file2.txt' ]
        '*'     | [:]                           | [ 'file1.txt', 'file2.txt', 'file3.log' ]
        '*'     | [type: 'file']                | [ 'file1.txt', 'file2.txt', 'file3.log' ]
        '*'     | [type: 'dir']                 | ['sub1']
        '*'     | [type: 'any']                 | [ 'file1.txt', 'file2.txt', 'file3.log', 'sub1' ]
        '**'    | [type: 'file']                | [ 'file1.txt', 'file2.txt', 'file3.log', 'file5.log' ]
        '**'    | [type: 'file', maxDepth: 0]   | ['file1.txt', 'file2.txt', 'file3.log' ]
        '{file1.txt,sub1/file5.log}'    | [:]   | ['file1.txt','file5.log']
    }

    def testFromPathIllegalArgument() {

        when:
        Channel.fromPath( '*', xx: 'any' )
        then:
        thrown( IllegalArgumentException )

        when:
        Channel.fromPath( '*', type: 'ciao' )
        then:
        thrown( IllegalArgumentException )

    }

    def testFromRelativePathWithGlob() {
        given:
        def folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('file1.txt'))
        def file2 = Files.createFile(folder.resolve('file2.txt'))
        def file3 = Files.createFile(folder.resolve('file3.log'))
        and:
        SysEnv.push(NXF_FILE_ROOT: folder.toString())

        when:
        def result = runDataflow {
            Channel.fromPath( '*.txt' )
                .map { path -> path.toString() }
                .toSortedList()
        }
        then:
        result.val == [ file1.toString(), file2.toString() ]

        when:
        result = runDataflow {
            Channel.fromPath( '*.txt', relative: true )
                .map { path -> path.toString() }
                .toSortedList()
        }
        then:
        result.val == [ file1.name, file2.name ]

        cleanup:
        SysEnv.pop()
    }

    def testFromRelativePathWithFileName() {
        given:
        def folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('file1.txt'))
        def file2 = Files.createFile(folder.resolve('file2.txt'))
        def file3 = Files.createFile(folder.resolve('file3.log'))
        and:
        SysEnv.push(NXF_FILE_ROOT: folder.toString())

        when:
        def result = runDataflow {
            Channel.fromPath( 'file3.log' )
                .map { path -> path.toString() }
                .toSortedList()
        }
        then:
        result.val == [ file3.toString() ]

        when:
        result = runDataflow {
            Channel.fromPath( 'file3.log', relative: true )
                .map { path -> path.toString() }
                .toSortedList()
        }
        then:
        result.val == [ file3.name ]

        cleanup:
        SysEnv.pop()
    }

    def testFromPathWithLinks() {

        setup:
        def folder = tempDir.root
        def file1 = Files.createFile(folder.resolve('file1.txt'))
        def file2 = Files.createFile(folder.resolve('file2.txt'))
        def sub1 = Files.createDirectories(folder.resolve('sub_1'))
        def file3 = Files.createFile(sub1.resolve('file3.txt'))
        def file4 = Files.createFile(sub1.resolve('file4.txt'))
        Files.createSymbolicLink(folder.resolve('link_to_sub1'), sub1 )

        // -- by default traverse symlinks
        when:
        def result = runDataflow {
            Channel.fromPath( folder.toAbsolutePath().toString() + '/**/*.txt' )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == ['file3.txt','file3.txt','file4.txt','file4.txt']

        // -- switch off symlinks traversing
        when:
        result = runDataflow {
            Channel.fromPath( folder.toAbsolutePath().toString() + '/**/*.txt', followLinks: false )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == ['file3.txt','file4.txt']

    }


    def testFromPathHidden() {

        setup:
        def folder = tempDir.root
        Files.createFile(folder.resolve('file1.txt'))
        Files.createFile(folder.resolve('file2.txt'))
        Files.createFile(folder.resolve('.file_hidden.txt'))

        // -- by default no hidden
        when:
        def result = runDataflow {
            Channel.fromPath( folder.toAbsolutePath().toString() + '/*.txt' )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == ['file1.txt','file2.txt']

        when:
        result = runDataflow {
            Channel.fromPath( folder.toAbsolutePath().toString() + '/.*.txt' )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == ['.file_hidden.txt']

        when:
        result = runDataflow {
            Channel.fromPath( folder.toAbsolutePath().toString() + '/*.txt', hidden: true )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == ['.file_hidden.txt', 'file1.txt','file2.txt']


    }

    def testFromPathWithListOfPatterns() {

        setup:
        def folder = tempDir.root.toAbsolutePath()
        folder.resolve('file1.txt').text = 'Hello'
        folder.resolve('file2.txt').text = 'Hola'
        folder.resolve('file3.fq').text = 'Ciao'

        when:
        def result = runDataflow {
            Channel.fromPath( ["$folder/*.txt", "$folder/*.fq"] )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == [ 'file1.txt', 'file2.txt', 'file3.fq' ]

        when:
        result = runDataflow {
            Channel.fromPath( ["$folder/file1.txt", "$folder/file2.txt", "$folder/file3.fq"] )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == [ 'file1.txt', 'file2.txt', 'file3.fq' ]

        when:
        result = runDataflow {
            Channel.fromPath( ["$folder/*"] )
                .map { path -> path.name }
                .toSortedList()
        }
        then:
        result.val == [ 'file1.txt', 'file2.txt', 'file3.fq' ]
    }

    def 'should check file exists' () {

        given:
        def folder = tempDir.root.toAbsolutePath()

        def file1 = folder.resolve('file1.txt')
        def file2 = folder.resolve('file2.txt')
        def file3 = folder.resolve('file3.txt')

        // only file1 and file3 exist
        file1.text = 'foo'
        file3.text = 'bar'

        when:
        def ch = runDataflow {
            Channel.fromPath(file1.toString()).toSortedList()
        }
        then:
        ch.getVal() == [file1]

        when:
        ch = runDataflow {
            Channel.fromPath([file1.toString(), file2.toString()]).toSortedList()
        }
        then:
        ch.getVal() == [file1, file2]

        when:
        ch = runDataflow {
            Channel.fromPath(file1.toString(), checkIfExists: true).toSortedList()
        }
        then:
        ch.getVal() == [file1]

        when:
        runDataflow {
            Channel.fromPath(file2.toString(), checkIfExists: true)
        }
        Channel.fromPath0Future.get()
        and:
        def session = Global.session
        then:
        session.aborted
        session.error instanceof NoSuchFileException
        session.error.message == file2.toString()

        when:
        runDataflow {
            Channel.fromPath([file1, file2, file3], checkIfExists: true)
        }
        Channel.fromPath0Future.get()
        and:
        session = Global.session
        then:
        session.aborted
        session.error instanceof NoSuchFileException
        session.error.message == file2.toString()

        when:
        runDataflow {
            Channel.fromPath('http://google.com/foo.txt', checkIfExists: true)
        }
        Channel.fromPath0Future.get()
        and:
        session = Global.session
        then:
        session.aborted
        session.error instanceof NoSuchFileException
        session.error.message == 'http://google.com/foo.txt'
    }

    def 'should check if pattern exists' () {

        given:
        def folder = Files.createTempDirectory('test'); folder.deleteDir()
        when:
        runDataflow {
            Channel.fromPath("$folder/*.txt", checkIfExists: true)
        }
        Channel.fromPath0Future.get()
        and:
        def session = Global.session
        then:
        session.isAborted()
        session.getError() instanceof IllegalArgumentException

    }

    def 'should check if pattern exists for multiple patterns' () {

        given:
        def folder = tempDir.root.toAbsolutePath()
        def file1 = folder.resolve('file1.txt'); file1.text = 'foo'

        when:
        def result = runDataflow {
            Channel.fromPath("$folder/*.txt", checkIfExists: true)
        }
        and:
        def session = Global.session
        then:
        result.getVal() instanceof Path
        !Global.session.terminated

        when:
        runDataflow {
            Channel.fromPath(["$folder/*.txt", 'foo/*.fq'], checkIfExists: true)
        }
        Channel.fromPath0Future.get()
        and:
        session = Global.session
        then:
        session.isAborted()
        session.getError() instanceof IllegalArgumentException

        cleanup:
        folder?.deleteDir()
    }

    def 'should check if pattern exists for filePairs' () {

        when:
        runDataflow {
            Channel.fromFilePairs('foo/*.txt', checkIfExists: true)
        }
        Channel.fromPath0Future.get()
        and:
        def session = Global.session
        then:
        session.isAborted()
        session.getError() instanceof IllegalArgumentException

    }

    def 'should return files prefix' () {

        expect:
        Channel.readPrefix(Paths.get('/some/path/abc1.fa'), '*1.fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), '*1.fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), '*_1.fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), '*_{1,2}.fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), '*_[1-2].fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), '**_{1,2}.*') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1_trimmed.fa'), '*_[1-2]_*.fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1_trimmed.fa'), '*??_[1-2]_*.fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), 'abc_{1,2}.fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), 'abc_[1-9].fa') == 'abc'
        Channel.readPrefix(Paths.get('/some/path/foo_abc_1.fa'), 'foo_*_{1,2}.fa') == 'foo_abc'
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), 'abc_1.fa') == 'abc_1'

        when:
        Channel.readPrefix(Paths.get('/some/path/abc_1.fa'), 'foo')
        then:
        thrown(IllegalArgumentException)
    }

    def 'should group files with the same prefix' () {

        setup:
        def folder = TestHelper.createInMemTempDir()
        def a1 = Files.createFile(folder.resolve('alpha_1.fa'))
        def a2 = Files.createFile(folder.resolve('alpha_2.fa'))
        def b1 = Files.createFile(folder.resolve('beta_1.fa'))
        def b2 = Files.createFile(folder.resolve('beta_2.fa'))
        def d1 = Files.createFile(folder.resolve('delta_1.fa'))
        def d2 = Files.createFile(folder.resolve('delta_2.fa'))

        when:
        def pairs = runDataflow {
            Channel.fromFilePairs(folder.resolve("*_{1,2}.*"))
        }
        then:
        pairs.val == ['alpha', [a1, a2]]
        pairs.val == ['beta', [b1, b2]]
        pairs.val == ['delta', [d1, d2]]
        pairs.val == Channel.STOP

        when:
        pairs = runDataflow {
            Channel.fromFilePairs(folder.resolve("*_{1,2}.fa") , flat: true)
        }
        then:
        pairs.val == ['alpha', a1, a2]
        pairs.val == ['beta', b1, b2]
        pairs.val == ['delta', d1, d2]
        pairs.val == Channel.STOP
    }

    def 'should group files with the same prefix and root path' () {

        setup:
        def folder = tempDir.root.toAbsolutePath()
        def a1 = Files.createFile(folder.resolve('aa_1.fa'))
        def a2 = Files.createFile(folder.resolve('aa_2.fa'))
        def x1 = Files.createFile(folder.resolve('xx_1.fa'))
        def x2 = Files.createFile(folder.resolve('xx_2.fa'))
        def z1 = Files.createFile(folder.resolve('zz_1.fa'))
        def z2 = Files.createFile(folder.resolve('zz_2.fa'))
        and:
        SysEnv.push(NXF_FILE_ROOT: folder.toString())

        when:
        def pairs = runDataflow {
            Channel.fromFilePairs("*_{1,2}.*").toSortedList { it[0] }
        }
        then:
        pairs.val == [
            ['aa', [a1, a2]],
            ['xx', [x1, x2]],
            ['zz', [z1, z2]]
        ]

        cleanup:
        SysEnv.pop()
    }

    def 'should group files with the same prefix using a custom grouping' () {

        setup:
        def folder = TestHelper.createInMemTempDir()
        def a1 = Files.createFile(folder.resolve('hello_1.fa'))
        def a2 = Files.createFile(folder.resolve('hello_2.fa'))
        def b1 = Files.createFile(folder.resolve('hola_1.fa'))
        def b2 = Files.createFile(folder.resolve('hola_2.fa'))
        def c1 = Files.createFile(folder.resolve('ciao_1.fa'))
        def c2 = Files.createFile(folder.resolve('ciao_2.fa'))

        when:
        def grouping = { Path file -> file.name.substring(0,1) }
        def pairs = runDataflow {
            Channel.fromFilePairs(folder.resolve("*_{1,2}.*"), grouping, size: -1)
        }
        then:
        pairs.val == ['c', [c1, c2]]
        pairs.val == ['h', [a1, a2, b1, b2]]
        pairs.val == Channel.STOP

    }

    def 'should group files with the same prefix and setting size' () {

        setup:
        def folder = TestHelper.createInMemTempDir()
        def a1 = Files.createFile(folder.resolve('alpha_1.fa'))
        def a2 = Files.createFile(folder.resolve('alpha_2.fa'))
        def a3 = Files.createFile(folder.resolve('alpha_3.fa'))

        def b1 = Files.createFile(folder.resolve('beta_1.fa'))
        def b2 = Files.createFile(folder.resolve('beta_2.fa'))
        def b3 = Files.createFile(folder.resolve('beta_3.fa'))

        def d1 = Files.createFile(folder.resolve('delta_1.fa'))
        def d2 = Files.createFile(folder.resolve('delta_2.fa'))
        def d3 = Files.createFile(folder.resolve('delta_3.fa'))
        def d4 = Files.createFile(folder.resolve('delta_4.fa'))

        when:
        // default size == 2
        def pairs = runDataflow {
            Channel.fromFilePairs(folder.resolve("*_{1,2,3}.fa"))
        }
        then:
        pairs.val == ['alpha', [a1, a2]]
        pairs.val == ['beta', [b1, b2]]
        pairs.val == ['delta', [d1, d2]]
        pairs.val == Channel.STOP

        when:
        pairs = runDataflow {
            Channel.fromFilePairs(folder.resolve("*_{1,2,3,4}.fa"), size: 3)
        }
        then:
        pairs.val == ['alpha', [a1, a2, a3]]
        pairs.val == ['beta', [b1, b2, b3]]
        pairs.val == ['delta', [d1, d2, d3]]
        pairs.val == Channel.STOP

        when:
        pairs = runDataflow {
            Channel.fromFilePairs(folder.resolve("*_{1,2,3,4}.fa"), size: -1)
        }
        then:
        pairs.val == ['alpha', [a1, a2, a3]]
        pairs.val == ['beta', [b1, b2, b3]]
        pairs.val == ['delta', [d1, d2, d3, d4]]
        pairs.val == Channel.STOP

    }

    def 'should group file with a collection of patterns' () {

        given:
        def folder = TestHelper.createInMemTempDir()
        def a1 = Files.createFile(folder.resolve('alpha_1.fa'))
        def a2 = Files.createFile(folder.resolve('alpha_2.fa'))
        def b1 = Files.createFile(folder.resolve('beta_1.fa'))
        def b2 = Files.createFile(folder.resolve('beta_2.fa'))

        def d1 = Files.createFile(folder.resolve('delta_1.fq'))
        def d2 = Files.createFile(folder.resolve('delta_2.fq'))
        def g1 = Files.createFile(folder.resolve('gamma_1.fq'))
        def g2 = Files.createFile(folder.resolve('gamma_2.fq'))

        when:
        def pairs = runDataflow {
            Channel.fromFilePairs([folder.resolve("*_{1,2}.fa"), folder.resolve("$folder/*_{1,2}.fq")])
        }
        then:
        pairs.val == ['alpha', [a1, a2]]
        pairs.val == ['beta', [b1, b2]]
        pairs.val == ['delta', [d1, d2]]
        pairs.val == ['gamma', [g1, g2]]
        pairs.val == Channel.STOP

    }

    def 'should return singleton group' () {
        given:
        def folder = TestHelper.createInMemTempDir()
        def a1 = Files.createFile(folder.resolve('alpha_1.fa'))
        def a2 = Files.createFile(folder.resolve('alpha_2.fa'))

        when:
        def files = runDataflow {
            Channel.fromFilePairs(folder.resolve('*.fa'), size: 1)
        }
        then:
        files.val == ['alpha_1', [a1]]
        files.val == ['alpha_2', [a2]]
        files.val == Channel.STOP
    }

    def 'should return singleton' () {
        given:
        def folder = TestHelper.createInMemTempDir()
        def a1 = Files.createFile(folder.resolve('alpha_1.fa'))

        when:
        def files = runDataflow {
            Channel.fromFilePairs(a1, size: 1)
        }
        then:
        files.val == ['alpha_1', [a1]]
        files.val == Channel.STOP
    }

    def 'should use size one by default' () {
        given:
        def folder = TestHelper.createInMemTempDir()
        def a1 = Files.createFile(folder.resolve('alpha_1.fa'))

        when:
        def files = runDataflow {
            Channel.fromFilePairs(a1)
        }
        then:
        files.val == ['alpha_1', [a1]]
        files.val == Channel.STOP
    }

    def 'should watch and emit a file' () {
        given:
        def folder = Files.createTempDirectory('test')

        when:
        def result = runDataflow {
            Channel.watchPath("$folder/").take(1)
        }
        sleep 500
        Files.createFile(folder.resolve('hello.txt'))
        then:
        result.val == folder.resolve('hello.txt')

        when:
        result = runDataflow {
            Channel.watchPath(folder.toString()).take(1)
        }
        sleep 500
        Files.createFile(folder.resolve('ciao.txt'))
        then:
        result.val == folder.resolve('ciao.txt')

        cleanup:
        folder?.deleteDir()
    }

    def 'should return an empty channel when watching a missing path' () {
        given:
        def folder = Files.createTempDirectory('test')
        when:
        def result = runDataflow {
            Channel.watchPath("$folder/foo/*")
        }
        then:
        result.val == Channel.STOP
        cleanup:
        folder?.deleteDir()
    }

    def 'should return single file entries' () {
        given:
        def files = [
                Paths.get('/data/SRR389222_sub1.fastq.gz'),
                Paths.get('/data/SRR389222_sub2.fastq.gz'),
                Paths.get('/data/SRR389222_sub3.fastq.gz')
        ]

        when:
        def result = runDataflow {
            Channel.fromFilePairs(files)
        }
        then:
        result.val == ['SRR389222_sub1', [Paths.get('/data/SRR389222_sub1.fastq.gz')]]
        result.val == ['SRR389222_sub2', [Paths.get('/data/SRR389222_sub2.fastq.gz')]]
        result.val == ['SRR389222_sub3', [Paths.get('/data/SRR389222_sub3.fastq.gz')]]
        result.val == Channel.STOP
    }

    def 'should check file pair exists' () {
        given:
        def file1 = Paths.get('/data/SRR389222_sub1.fastq.gz')
        def file2 = Paths.get('/data/SRR389222_sub2.fastq.gz')

        when:
        runDataflow {
            Channel.fromFilePairs([file1, file2], checkIfExists: true)
        }
        Channel.fromPath0Future.get()
        and:
        def session = Global.session
        then:
        session.aborted
        session.error instanceof NoSuchFileException
        session.error.message == file1.toString()
    }

    def 'should return single ftp entries' () {
        given:
        def ftp1 = FileHelper.asPath('ftp://foo.com/data/SRR389222_sub1.fastq.gz')
        def ftp2 = FileHelper.asPath('ftp://foo.com/data/SRR389222_sub2.fastq.gz')

        when:
        def result = runDataflow {
            Channel.fromFilePairs([ftp1, ftp2])
        }
        then:
        result.val == ['SRR389222_sub1', [ftp1]]
        result.val == ['SRR389222_sub2', [ftp2]]
        result.val == Channel.STOP

    }

    def 'should not fail when setting SRA correct properties' () {
        given:
        def id = 'SRR389222'
        def retryPolicy = [maxAttempts: 2]

        when:
        def result = runDataflow {
            Channel.fromSRA(id, apiKey: '1234', retryPolicy: retryPolicy, cache: false, max: 10, protocol: 'http')
        }
        then:
        result != null

    }

    def 'should fail when SRA incorrect property' () {
        when:
        def result = runDataflow {
            Channel.fromSRA('SRR389222', incorrectKey: '1234')
        }
        then:
        thrown(IllegalArgumentException)
    }

}
