struct

  type input_fastq = [
    | `Paired_end of File.t list * File.t list
    | `Single_end of File.t list
  ]

  let input_fastq ~dataset (fastqs: input_fastq) =
    let is_fastq_gz p =
      Filename.check_suffix p "fastq.gz" || Filename.check_suffix p "fq.gz"  in
    let is_fastq p =
      Filename.check_suffix p "fastq" || Filename.check_suffix p "fq"  in
    let theyre_all l f = List.for_all l ~f:(fun file -> f file#product#path) in
    let bring_to_single_fastq l =
      match l with
      | [] -> failwithf "Dataset %S seems empty" dataset
      | gzs when theyre_all gzs is_fastq_gz ->
        Gunzip_concat  (List.map gzs (fun f -> Fastq_gz f))
      | fqs when theyre_all fqs is_fastq ->
        Concat_text (List.map fqs (fun f -> Fastq f))
      | not_supported ->
        failwithf
          "For now, a sample must be a uniform list of fastq.gz/fq.gz or .fq/.fastq files. Dataset %S does not qualify: [%s]
          "

          dataset
          (List.map not_supported ~f:(fun f -> Filename.basename f#product#path)
           |> String.concat ~sep:", ")
    in
    let sample_info = {sample_name = dataset; fragment_id = dataset} in
    match fastqs with
    | `Paired_end (l1, l2) ->
      Paired_end_sample (sample_info, bring_to_single_fastq l1, bring_to_single_fastq l2)
    | `Single_end l ->
      Single_end_sample (sample_info, bring_to_single_fastq l)

  let bam ~dataset bam = Bam_sample (dataset, bam)

  let bam_to_fastq ?sample_name how bam = Bam_to_fastq (sample_name, how, bam)

  let bwa ?(configuration = Bwa.Configuration.Aln.default) fastq =
    Bwa (configuration, fastq)

  let bwa_aln = bwa

  let bwa_mem ?(configuration = Bwa.Configuration.Mem.default) fastq =
    Bwa_mem (configuration, fastq)

  let mosaik fastq = Mosaik fastq

  let star ?(configuration = Star.Configuration.Align.default) fastq =
    Star (configuration, fastq)

  let hisat ?(configuration = Hisat.Configuration.default_v1) fastq =
    Hisat (configuration, fastq)

  let stringtie ?(configuration = Stringtie.Configuration.default) bam =
    Stringtie (configuration, bam)

  let gatk_indel_realigner
        ?(configuration=Gatk.Configuration.default_indel_realigner)
        bam
    = Gatk_indel_realigner (configuration, bam)
  let picard_mark_duplicates
      ?(settings=Picard.Mark_duplicates_settings.default) bam =
    Picard_mark_duplicates (settings, bam)

  let gatk_bqsr ?(configuration=Gatk.Configuration.default_bqsr) bam = Gatk_bqsr (configuration, bam)

  let pair ~normal ~tumor = Bam_pair (normal, tumor)

  let germline_variant_caller t input_bam =
    Germline_variant_caller (t, input_bam)

  let gatk_haplotype_caller input_bam =
    let configuration_name = "default" in
    let configuration_json =
      `Assoc [
        "Name"`String configuration_name;
      ] in
    let make_target
        ~run_with ~input ~result_prefix ?more_edges () =
      match input with
      | Variant_caller.Germline input_bam ->
        Gatk.haplotype_caller ?more_edges ~run_with
          ~input_bam ~result_prefix `Map_reduce in
    germline_variant_caller
      {Variant_caller.name = "Gatk-HaplotypeCaller";
        configuration_json;
        configuration_name;
        make_target;}
      input_bam

  let somatic_variant_caller t bam_pair =
    Somatic_variant_caller (t, bam_pair)

  let mutect ?(configuration=Mutect.Configuration.default) bam_pair =
    let configuration_name = configuration.Mutect.Configuration.name in
    let configuration_json = Mutect.Configuration.to_json configuration in
    let make_target
        ~run_with ~input ~result_prefix ?more_edges () =
      match input with | Variant_caller.Somatic {normal; tumor} ->
      Mutect.run
        ~configuration
        ?more_edges
        ~run_with
        ~normal ~tumor
        ~result_prefix `Map_reduce in
    somatic_variant_caller
      {Variant_caller.name = "Mutect";
       configuration_json;
       configuration_name;
       make_target;}
      bam_pair

  let mutect2 ?(configuration=Gatk.Configuration.Mutect2.default) bam_pair =
    let configuration_name = configuration.Gatk.Configuration.Mutect2.name in
    let configuration_json = Gatk.Configuration.Mutect2.to_json configuration in
    let make_target
        ~run_with ~input ~result_prefix ?more_edges () =
      match input with
      | Variant_caller.Somatic {normal; tumor} ->
        Gatk.mutect2
          ~configuration ?more_edges ~run_with
          ~input_normal_bam:normal ~input_tumor_bam:tumor
          ~result_prefix `Map_reduce in
    somatic_variant_caller
      {Variant_caller.name = "Mutect";
       configuration_json;
       configuration_name;
       make_target;}
      bam_pair

  let somaticsniper
      ?(configuration = Somaticsniper.Configuration.default)
      bam_pair =
    let make_target
        ~run_with ~input ~result_prefix ?more_edges () =
      match input with
      | Variant_caller.Somatic {normal; tumor} ->
        Somaticsniper.run
          ~configuration ~run_with ~normal ~tumor ~result_prefix () in
    somatic_variant_caller
      {Variant_caller.name = "Somaticsniper";
       configuration_json = Somaticsniper.Configuration.to_json configuration;
       configuration_name = Somaticsniper.Configuration.name configuration;
       make_target;}
      bam_pair

  let varscan_somatic ?adjust_mapq bam_pair =
    let configuration_name =
      sprintf "amq-%s"
        (Option.value_map ~default:"NONE" adjust_mapq ~f:Int.to_string) in
    let configuration_json =
      `Assoc [
        "Name"`String configuration_name;
        "Adjust_mapq",
        `String (Option.value_map adjust_mapq ~f:Int.to_string ~default:"None");
      ] in
    somatic_variant_caller
      {Variant_caller.name = "Varscan-somatic";
       configuration_json;
       configuration_name;
       make_target = begin
         fun ~run_with ~input ~result_prefix ?more_edges () ->
           match input with | Variant_caller.Somatic {normal; tumor} ->
           Varscan.somatic_map_reduce ?adjust_mapq
             ?more_edges ~run_with ~normal ~tumor ~result_prefix ()
       end}
      bam_pair

  let strelka ~configuration bam_pair =
    somatic_variant_caller
      {Variant_caller.name = "Strelka";
       configuration_json = Strelka.Configuration.to_json configuration;
       configuration_name = configuration.Strelka.Configuration.name;
       make_target =
         fun ~run_with ~input ~result_prefix ?more_edges () ->
            match input with | Variant_caller.Somatic {normal; tumor} ->
            Strelka.run
              ?more_edges
              ~configuration ~normal ~tumor
              ~run_with ~result_prefix
              ()
      }
      bam_pair

  let virmid ~configuration bam_pair =
    somatic_variant_caller
      {Variant_caller.name = "Virmid";
       configuration_json = Virmid.Configuration.to_json configuration;
       configuration_name = configuration.Virmid.Configuration.name;
       make_target =
          fun ~run_with ~input ~result_prefix
            ?more_edges () ->
            match input with | Variant_caller.Somatic {normal; tumor} ->
            Virmid.run
              ?more_edges
              ~configuration ~normal ~tumor
              ~run_with ~result_prefix
              ()
      }
      bam_pair

  let muse ~configuration bam_pair =
    let make_target
        ~(run_with: Machine.t) ~input ~result_prefix
        ?more_edges () =
      match input with | Variant_caller.Somatic {normal; tumor} ->
      Muse.run ~configuration ?more_edges
        ~run_with ~normal ~tumor ~result_prefix `Map_reduce in
    somatic_variant_caller
      {Variant_caller.name = "Muse";
       configuration_json = Muse.Configuration.to_json configuration;
       configuration_name = configuration.Muse.Configuration.name;
       make_target }
      bam_pair

  let seq2hla fastq_sample = Seq2HLA fastq_sample

  let optitype kind fastq_sample = Optitype (kind, fastq_sample)

  let add_tags ?(recursively = false) tags pipeline =
    With_metadata ((if recursively then `Add_tags_rec tags else `Add_tags tags),
                   pipeline)

end