/****
DIAMOND protein sequence aligner
Copyright (C) 2012-2026 Benjamin J. Buchfink

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
****/
// SPDX-License-Identifier: GPL-3.0-or-later

#include <memory>
#include <iostream>
#include <numeric>
#include <thread>
#include <chrono>
#include "util/command_line_parser.h"
#include "config.h"
#include "util/util.h"
#include "util/log_stream.h"
#include "basic/value.h"
#include "stats/score_matrix.h"
#include "util/sequence/translate.h"
#include "masking/masking.h"
#include "util/system/system.h"
#include "util/simd.h"
#include "search/search.h"
#include "stats/cbs.h"
#include "basic/shape.h"
#include "output/output_format.h"
#include "data/sequence_file.h"
#include "util/math/math.h"

using std::runtime_error;
using std::thread;
using std::stringstream;
using std::endl;
using std::ostream;
using std::unique_ptr;
using std::string;
using std::pair;
using std::cout;
using std::cerr;
using std::vector;

const EMap<Sensitivity> EnumTraits<Sensitivity>::to_string = {
	{ Sensitivity::FASTER, "faster" },
	{ Sensitivity::FAST, "fast" },
	{ Sensitivity::DEFAULT, "default" },
	{ Sensitivity::LINCLUST_40, "linclust-40" },
	{ Sensitivity::LINCLUST_20, "linclust-20" },
	{ Sensitivity::MID_SENSITIVE, "mid-sensitive" },
	{ Sensitivity::SHAPES6x10, "shapes-6x10" },
	{ Sensitivity::SHAPES30x10, "shapes-30x10" },
	{ Sensitivity::SENSITIVE, "sensitive" },
	{ Sensitivity::MORE_SENSITIVE, "more-sensitive" },
	{ Sensitivity::VERY_SENSITIVE, "very-sensitive" },
	{ Sensitivity::ULTRA_SENSITIVE, "ultra-sensitive" }
};

const SEMap<Sensitivity> EnumTraits<Sensitivity>::from_string = {
	{ "faster", Sensitivity::FASTER },
	{ "fast", Sensitivity::FAST },
	{ "default", Sensitivity::DEFAULT },
	{ "mid-sensitive", Sensitivity::MID_SENSITIVE },
	{ "shapes-6x10", Sensitivity::SHAPES6x10 },
	{ "shapes-30x10", Sensitivity::SHAPES30x10 },
	{ "linclust-40", Sensitivity::LINCLUST_40 },
	{ "linclust-20", Sensitivity::LINCLUST_20 },
	{ "sensitive", Sensitivity::SENSITIVE },
	{ "more-sensitive", Sensitivity::MORE_SENSITIVE },
	{ "very-sensitive", Sensitivity::VERY_SENSITIVE },
	{ "ultra-sensitive", Sensitivity::ULTRA_SENSITIVE }
};

const SEMap<GraphAlgo> EnumTraits<GraphAlgo>::from_string = {
	{ "gvc", GraphAlgo::GREEDY_VERTEX_COVER },
	{ "len", GraphAlgo::LEN_SORTED }
};

const EMap<Config::Algo> EnumTraits<Config::Algo>::to_string = { { Config::Algo::DOUBLE_INDEXED, "Double-indexed" }, { Config::Algo::QUERY_INDEXED, "Query-indexed"}, {Config::Algo::CTG_SEED, "Query-indexed with contiguous seed"} };
const SEMap<Config::Algo> EnumTraits<Config::Algo>::from_string = { {"", Config::Algo::AUTO}, { "0", Config::Algo::DOUBLE_INDEXED}, {"1", Config::Algo::QUERY_INDEXED}, {"ctg", Config::Algo::CTG_SEED} };

const EMap<SequenceType> EnumTraits<SequenceType>::to_string = { {SequenceType::amino_acid,"prot"}, { SequenceType::nucleotide,"nucl"} };
const SEMap<SequenceType> EnumTraits<SequenceType>::from_string = { {"prot",SequenceType::amino_acid}, {"nucl",SequenceType::nucleotide} };

Config config;

pair<double, int> block_size(int64_t memory_limit, int64_t db_letters, Sensitivity s, bool lin, int thread_count, bool mutual_cov) {
	const double MIN_BLOCK_SIZE = 0.001;
	const double AVG_SEQ_LENGTH_EST = 200;
	const double m = (double)memory_limit / 1e9;
	const int min = Search::sensitivity_traits.at(s).minimizer_window,
		sketch_size = Search::sensitivity_traits.at(s).sketch_size,
		max_c = min > 0 || sketch_size > 0 ? 1 : (lin ? 16 : 4),
		shape_weight = Shape(Search::shape_codes.at(s).front().c_str(), 0).weight_;
	const double max_b = lin ? 32768 : (s <= Sensitivity::DEFAULT ? 12.0 : (s <= Sensitivity::MORE_SENSITIVE ? 6.0 : 1.6));
	// c = m < 40.0 && s <= Sensitivity::MORE_SENSITIVE && min == 0 && sketch_size == 0 ? 4 : 1;
	assert(min == 0 || sketch_size == 0);
	int c = 0;
	double b;
	if (lin && !mutual_cov) {
		return { std::max(m / 10.0, MIN_BLOCK_SIZE),1 };
	}
	do {
		++c;
		double seeds_per_letter = (sketch_size > 0 ? 1 / AVG_SEQ_LENGTH_EST * sketch_size : 1.0) / c;
		if (min > 0)
			seeds_per_letter /= (double)min / 2;
		const double hash_join_factor = 1.0 + (double)thread_count / (seedp_count(Search::seedp_bits(shape_weight, thread_count, c)) / c);
		const double seed_array_entry_size = 18.0 * hash_join_factor;
		b = m / (seed_array_entry_size * seeds_per_letter + 2.0);
	} while (std::round(b * 1e9) < db_letters && b < max_b && c < max_c);
	/*if (b > 4)
		b = floor(b);
	else if (b > 0.4)
		b = floor(b * 10) / 10;
	else
		b = floor(b * 1000) / 1000;*/
	if (!config.no_block_size_limit)
		b = std::min(b, max_b);
	return { std::max(b, MIN_BLOCK_SIZE), c };
}

template<typename T>
T set_string_option(const string& s, const string& name, const vector<pair<string, T>>& values) {
	if (s.empty())
		return (T)0;
	for (auto& i : values)
		if (s == i.first)
			return i.second;
	throw std::runtime_error("Invalid argument for option " + name + ". Allowed values are:" + std::accumulate(values.begin(), values.end(), string(), [](const string& s, const pair<string, T>& v) { return s + ' ' + v.first; }));
}

void Config::set_sens(Sensitivity sens) {
	if (sensitivity != Sensitivity::DEFAULT)
		throw runtime_error("Sensitivity switches are mutually exclusive.");
	sensitivity = sens;
}

string Config::single_query_file() const {
	return query_file.empty() ? string() : query_file.front();
}

CompressionLib Config::compressor() const
{
	if (compression.empty() || compression == "0")
		return CompressionLib::NONE;
	else if (compression == "1")
		return CompressionLib::ZLIB;
	else if (compression == "zstd")
		return CompressionLib::ZSTD;
	else
		throw runtime_error("Invalid compression algorithm: " + compression);
}

Config::Config(int argc, const char **argv, bool check_io, CommandLineParser& parser)
{
	parser.add_command("makedb", "Build DIAMOND database from a FASTA file", makedb)
		.add_command("prepdb", "", prep_db)
		.add_command("blastp", "Align amino acid query sequences against a protein reference database", blastp)
		.add_command("blastx", "Align DNA query sequences against a protein reference database", blastx)
		.add_command("cluster", "Cluster protein sequences", cluster)
		.add_command("linclust", "Cluster protein sequences in linear time", LINCLUST)
		.add_command("realign", "Realign clustered sequences against their centroids", CLUSTER_REALIGN)
		.add_command("recluster", "Recompute clustering to fix errors", RECLUSTER)
		.add_command("reassign", "Reassign clustered sequences to the closest centroid", CLUSTER_REASSIGN)
		.add_command("view", "View DIAMOND alignment archive (DAA) formatted file", view)
		.add_command("merge-daa", "Merge DAA files", MERGE_DAA)
		.add_command("help", "Produce help message", help)
		.add_command("version", "Display version information", version)
		.add_command("getseq", "Retrieve sequences from a DIAMOND database file", getseq)
		.add_command("dbinfo", "Print information about a DIAMOND database file", dbinfo)
		.add_command("test", "Run regression tests", regression_test)
		.add_command("makeidx", "Make database index", makeidx)
		.add_command("greedy-vertex-cover", "Compute greedy vertex cover", GREEDY_VERTEX_COVER)
		//.add_command("composition-matrix", "Compute compositionally adjusted BLOSUM62 matrices for all sequence pairs", COMPOSITION_MATRIX)
		.add_command("roc", "", roc)
		.add_command("benchmark", "", benchmark)
		.add_command("deepclust", "", DEEPCLUST)
		.add_command("countdistinct", "Count distinct sequences in a FASTA file", COUNT_DISTINCT)
#ifdef EXTRA
		.add_command("random-seqs", "", random_seqs)
		.add_command("sort", "", sort)
		.add_command("dbstat", "", db_stat)
		.add_command("mask", "", mask)
		.add_command("fastq2fasta", "", fastq2fasta)
		.add_command("info", "", info)
		.add_command("seed-stat", "", seed_stat)
		.add_command("smith-waterman", "", smith_waterman)
		.add_command("simulate-seqs", "", simulate_seqs)
		.add_command("split", "", split)
		.add_command("upgma", "", upgma)
		.add_command("upgmamc", "", upgma_mc)
		.add_command("reverse", "", reverse_seqs)
		.add_command("compute-medoids", "", compute_medoids)
		.add_command("mutate", "", mutate)
		.add_command("roc-id", "", rocid)
		.add_command("find-shapes", "", find_shapes)
		.add_command("hashseqs", "", HASH_SEQS)
		.add_command("listseeds", "", LIST_SEEDS)
		.add_command("blastn", "Align DNA query sequences against a DNA reference database", blastn)
		.add_command("wc", "", WORD_COUNT)
		.add_command("cut", "", CUT)
		.add_command("model-seqs", "", MODEL_SEQS)
	    .add_command("make-seed-table", "", MAKE_SEED_TABLE)
#endif
		;

	auto& general = parser.add_group("General options", { makedb, blastp, blastx, cluster, view, getseq, dbinfo, makeidx, CLUSTER_REALIGN, GREEDY_VERTEX_COVER, DEEPCLUST, RECLUSTER, MERGE_DAA, LINCLUST, CLUSTER_REASSIGN, COMPOSITION_MATRIX, COUNT_DISTINCT });
	general.add()
		("threads", 'p', "number of CPU threads", threads_)
		("log", 0, "enable debug log", debug_log)
		("quiet", 0, "disable console output", quiet)
		("tmpdir", 't', "directory for temporary files", tmpdir)
		("keep-temp-files", 0, "do not delete temporarary files", keep_temp_files);

	auto& general_db = parser.add_group("General/database options", { makedb, blastp, blastx, cluster, getseq, dbinfo, makeidx, CLUSTER_REALIGN, GREEDY_VERTEX_COVER, DEEPCLUST, RECLUSTER, LINCLUST, CLUSTER_REASSIGN, COMPOSITION_MATRIX });
	general_db.add()
		("db", 'd', "database file", database);

	auto& general_out = parser.add_group("General/output", { blastp, blastx, cluster, view, getseq, CLUSTER_REALIGN, GREEDY_VERTEX_COVER, DEEPCLUST, RECLUSTER, MERGE_DAA, LINCLUST, CLUSTER_REASSIGN, COMPOSITION_MATRIX, COUNT_DISTINCT });
	general_out.add()
		("out", 'o', "output file", output_file);

	auto& general_out2 = parser.add_group("General/output2", { blastp, blastx, cluster, view, CLUSTER_REALIGN, GREEDY_VERTEX_COVER, DEEPCLUST, RECLUSTER, LINCLUST, CLUSTER_REASSIGN });
	general_out2.add()
		("header", 0, "Use header lines in tabular output format (0/simple/verbose).", output_header, Option<vector<string>>(), 0);
	
    string dbstring;
	auto& makedb_opt = parser.add_group("Makedb options", { makedb, MERGE_DAA, COUNT_DISTINCT });
	makedb_opt.add()
		("in", 0, "input reference file in FASTA format/input DAA files for merge-daa", input_ref_file);

	auto& makedb_tax_opt = parser.add_group("Makedb/taxon options", { makedb });
	makedb_tax_opt.add()
		("taxonmap", 0, "protein accession to taxid mapping file", prot_accession2taxid)
		("taxonnodes", 0, "taxonomy nodes.dmp from NCBI", nodesdmp)
		("taxonnames", 0, "taxonomy names.dmp from NCBI", namesdmp);

	auto& align_clust_realign = parser.add_group("Aligner/Clustering/Realign options", { blastp, blastx, cluster, RECLUSTER, CLUSTER_REASSIGN, DEEPCLUST, CLUSTER_REALIGN, LINCLUST, COMPOSITION_MATRIX });
	align_clust_realign.add()
		("comp-based-stats", 0, "composition based statistics mode (0-6)", comp_based_stats_)
		("relative-entropy-tolerance", 0, "tolerance for matrix adjust relative entropy (default=0.001)", relative_entropy_tolerance, 0.001)
		("masking", 0, "masking algorithm (none, seg, seg-all, tantan=default)", masking_)
		("soft-masking", 0, "soft masking (none=default, seg, tantan)", soft_masking)
		("no-block-size-limit", 0, "", no_block_size_limit);

	auto& align_clust = parser.add_group("Aligner/Clustering options", { blastp, blastx, cluster, RECLUSTER, CLUSTER_REASSIGN, DEEPCLUST, LINCLUST });
	align_clust.add()
		("gapopen", 0, "gap open penalty", gap_open, -1)
		("gapextend", 0, "gap extension penalty", gap_extend, -1)
		("matrix", 0, "score matrix for protein alignment (default=BLOSUM62)", matrix, string("blosum62"))
		("custom-matrix", 0, "file containing custom scoring matrix", matrix_file)
		("evalue", 'e', "maximum e-value to report alignments (default=0.001)", max_evalue, 0.001)
		("motif-masking", 0, "softmask abundant motifs (0/1)", motif_masking)
		("approx-id", 0, "minimum approx. identity% to report an alignment/to cluster sequences", approx_min_id)
		("id", 0, "minimum identity% to report an alignment", min_id)
		("ext", 0, "Extension mode (banded-fast/banded-slow/full/global/none)", extension_mode)
		("min-len-ratio", 0, "sequence length ratio cutoff for mutual coverage", min_length_ratio);

	auto& aligner_view = parser.add_group("Aligner/view options", { blastp, blastx, view });
	aligner_view.add()
		("max-target-seqs", 'k', "maximum number of target sequences to report alignments for (default=25)", max_target_seqs_)
		("top", 0, "report alignments within this percentage range of top alignment score (overrides --max-target-seqs)", toppercent);

	auto& aligner_sens = parser.add_group("Aligner/sens options", { blastp, blastx, makeidx });
	aligner_sens.add()
		("faster", 0, "enable faster mode", mode_faster)
		("fast", 0, "enable fast mode", mode_fast)
		("mid-sensitive", 0, "enable mid-sensitive mode", mode_mid_sensitive)
		("linclust-20", 0, "enable mode for linear search at 20% identity", mode_linclust_20)
		("shapes-6x10", 0, "enable mode using 30 seed shapes of weight 10", mode_shapes6x10)
		("shapes-30x10", 0, "enable mode using 30 seed shapes of weight 10", mode_shapes30x10)
		("sensitive", 0, "enable sensitive mode)", mode_sensitive)
		("more-sensitive", 0, "enable more sensitive mode", mode_more_sensitive)
		("very-sensitive", 0, "enable very sensitive mode", mode_very_sensitive)
		("ultra-sensitive", 0, "enable ultra sensitive mode", mode_ultra_sensitive)
		("shapes", 's', "number of seed shapes (default=all available)", shapes);

	auto& aligner = parser.add_group("Aligner options", { blastp, blastx });
	aligner.add()
		("query", 'q', "input query file", query_file)
		("strand", 0, "query strands to search (both/minus/plus)", query_strands, string("both"))
		("un", 0, "file for unaligned queries", unaligned)
		("al", 0, "file or aligned queries", aligned_file)
		("unfmt", 0, "format of unaligned query file (fasta/fastq)", unfmt, string("fasta"))
		("alfmt", 0, "format of aligned query file (fasta/fastq)", alfmt, string("fasta"))
		("unal", 0, "report unaligned queries (0=no, 1=yes)", report_unaligned, -1)
		("max-hsps", 0, "maximum number of HSPs per target sequence to report for each query (default=1)", max_hsps, 1u)
		("range-culling", 0, "restrict hit culling to overlapping query ranges", query_range_culling)
		("compress", 0, "compression for output files (0=none, 1=gzip, zstd)", compression)
		("min-score", 0, "minimum bit score to report alignments (overrides e-value setting)", min_bit_score)
		("query-cover", 0, "minimum query cover% to report an alignment", query_cover)
		("subject-cover", 0, "minimum subject cover% to report an alignment", subject_cover)
		("swipe", 0, "exhaustive alignment against all database sequences", swipe_all)
		("iterate", 0, "iterated search with increasing sensitivity", iterate, Option<vector<string>>(), 0)
		("global-ranking", 'g', "number of targets for global ranking", global_ranking_targets)
		("block-size", 'b', "sequence block size in billions of letters (default=2.0)", chunk_size)
		("index-chunks", 'c', "number of chunks for index processing (default=4)", lowmem_)
		("frameshift", 'F', "frame shift penalty (default=disabled)", frame_shift)
		("long-reads", 0, "short for --range-culling --top 10 -F 15", long_reads)
		("query-gencode", 0, "genetic code to use to translate query (see user manual)", query_gencode, 1u)
		("salltitles", 0, "include full subject titles in DAA file", salltitles)
		("sallseqid", 0, "include all subject ids in DAA file", sallseqid)
		("no-self-hits", 0, "suppress reporting of identical self hits", no_self_hits)
		("taxonlist", 0, "restrict search to list of taxon ids (comma-separated)", taxonlist)
		("taxon-exclude", 0, "exclude list of taxon ids (comma-separated)", taxon_exclude)
		("seqidlist", 0, "filter the database by list of accessions", seqidlist)
		("skip-missing-seqids", 0, "ignore accessions missing in the database", skip_missing_seqids)
		("symmetrize-evalue", 0, "symmetrize evalues over queries and targets", symmetrize_evalue);

	SequenceFile::init_taxon_output_fields();
	std::ostringstream format_str;
	format_str << "output format\n\
\t0   = BLAST pairwise\n\
\t5   = BLAST XML\n\
\t6   = BLAST tabular\n\
\t100 = DIAMOND alignment archive (DAA)\n\
\t101 = SAM\n\
\t102 = Taxonomic classification\n\
\t103 = PAF\n\
\t104 = JSON (flat)\n\n\
\tValues 6 and 104 may be followed by a space-separated list of these keywords:\n\n";

	const auto l = [&] {
		vector<size_t> l;
		std::transform(TabularFormat::field_def.begin(), TabularFormat::field_def.end(), std::back_inserter(l), [](const pair<FieldId, OutputField>& p) { return p.second.key.length(); });
		return l;
		}();
	const size_t max_len = *std::max_element(l.begin(), l.end());

	for (auto i : TabularFormat::field_def)
		format_str << "\t" << std::setw(max_len) << i.second.key << " means " << i.second.description << "\n";

	format_str << "\n\tDefault: qseqid sseqid pident length mismatch gapopen qstart qend sstart send evalue bitscore\n";

	auto& format = parser.add_group("Output format options", { blastp, blastx, view, CLUSTER_REALIGN });
	format.add()
		("outfmt", 'f', format_str.str().c_str(), output_format);

	auto& taxon_format = parser.add_group("Taxon output format options", { blastp, blastx });
	taxon_format.add()
		("include-lineage", 0, "Include lineage in the taxonomic classification format", include_lineage);

	auto& cluster_opt = parser.add_group("Clustering options", { cluster, RECLUSTER, DEEPCLUST, LINCLUST });
	
	cluster_opt.add()
		("cluster-steps", 0, "Clustering rounds for cascaded clustering", cluster_steps)
		("kmer-ranking", 0, "Rank sequences based on kmer frequency in linear stage", kmer_ranking)
		("round-coverage", 0, "Per-round coverage cutoffs for cascaded clustering", round_coverage)
		("round-approx-id", 0, "Per-round approx-id cutoffs for cascaded clustering", round_approx_id)
		("aln-out", 0, "Output file for clustering alignments", aln_out)
		("reps", 0, "Output file for representative sequences in FASTA format. Only includes id and sequence (no additional header data).", reps_out)
		("single-step", 0, "Perform one computational step of clustering then exit", single_step)
		("linclust-minichunk", 0, "Minimal chunk size for linclust (default=auto)", linclust_minichunk);

	auto& memory_opt = parser.add_group("Memory options", { cluster, RECLUSTER, CLUSTER_REASSIGN, GREEDY_VERTEX_COVER, DEEPCLUST, LINCLUST, CLUSTER_REALIGN, COUNT_DISTINCT });
	memory_opt.add()
		("memory-limit", 'M', "Memory limit in GB (default = 16G)", memory_limit);

	auto& cluster_reassign_opt = parser.add_group("Clustering/reassign options", { cluster, RECLUSTER, CLUSTER_REASSIGN, GREEDY_VERTEX_COVER, DEEPCLUST, LINCLUST });
	cluster_reassign_opt.add()
		("member-cover", 0, "Minimum coverage% of the cluster member sequence (default=80.0)", member_cover)
		("mutual-cover", 0, "Minimum mutual coverage% of the cluster member and representative sequence", mutual_cover)
		("no-reassign", 0, "Do not reassign to closest representative", no_gvc_reassign);

	auto& gvc_opt = parser.add_group("GVC options", { GREEDY_VERTEX_COVER });
	gvc_opt.add()
		("centroid-out", 0, "Output file for centroids", centroid_out)
		("edges", 0, "Input file for greedy vertex cover", edges)
		("edge-format", 0, "Edge format for greedy vertex cover (default/triplet)", edge_format)
		("max-oid", 0, "Maximum ordinal vertex ID (enables direct numeric indexing)", max_oid)
		("symmetric", 0, "Edges are symmetric", symmetric);

	auto& realign_opt = parser.add_group("Cluster input options", { CLUSTER_REALIGN, RECLUSTER, CLUSTER_REASSIGN });
	realign_opt.add()
		("clusters", 0, "Clustering input file mapping sequences to representatives", clustering);

#ifdef WITH_DNA
    string dna_extension_string;
#endif

	auto& advanced_gen = parser.add_group("Advanced/general", { blastp, blastx, blastn, CLUSTER_REASSIGN, regression_test, cluster, DEEPCLUST, LINCLUST, makedb });
	advanced_gen.add()
		("file-buffer-size", 0, "file buffer size in bytes (default=67108864)", file_buffer_size, INT64_C(67108864))
		("no-unlink", 0, "Do not unlink temporary files.", no_unlink)
		("ignore-warnings", 0, "Ignore warnings", ignore_warnings)
		("no-parse-seqids", 0, "Print raw seqids without parsing", no_parse_seqids);

	auto& advanced_aln_cluster = parser.add_group("Advanced options aln/cluster", { blastp, blastx, blastn, CLUSTER_REASSIGN, regression_test, cluster, DEEPCLUST, LINCLUST, RECLUSTER });
	advanced_aln_cluster.add()
		("parallel-tmpdir", 0, "directory for temporary files used by multiprocessing", parallel_tmpdir)
		("bin", 0, "number of query bins for seed search", query_bins)
		("ext-chunk-size", 0, "chunk size for adaptive ranking (default=auto)", ext_chunk_size)
		("no-ranking", 0, "disable ranking heuristic", no_ranking)
		("dbsize", 0, "effective database size (in letters)", db_size)
		("no-auto-append", 0, "disable auto appending of DAA and DMND file extensions", no_auto_append)
		("tantan-minMaskProb", 0, "minimum repeat probability for masking (default=0.9)", tantan_minMaskProb, 0.9)
		("oid-output", 0, "Output OIDs instead of accessions (clustering)", oid_output)
		("swipe-task-size", 0, "task size for DP parallelism (100000000)", swipe_task_size, INT64_C(100000000))
		("anchored-swipe", 0, "Enable anchored SWIPE extension", anchored_swipe)
		("query-match-distance-threshold", 0, "Matrix adjust threshold", query_match_distance_threshold, -1.0)
		("length-ratio-threshold", 0, "Matrix adjust threshold", length_ratio_threshold, -1.0)
		("cbs-angle", 0, "Matrix adjust threshold", cbs_angle, -1.0)
		("hit-membuf", 0, "Buffer intermediate hits in memory", hit_membuf)
		("fpu-compat", 0, "Floating point operations compatibility mode", fpu_compat)
		("no-mempool", 0, "Disable per-thread memory pool", no_mem_pool);

	auto& advanced = parser.add_group("Advanced options", { blastp, blastx, blastn, regression_test });
	advanced.add()
		("algo", 0, "Seed search algorithm (0=double-indexed/1=query-indexed/ctg=contiguous-seed)", algo_str)
		("min-orf", 'l', "ignore translated sequences without an open reading frame of at least this length", run_len)
		("min-query-len", 0, "filter query sequences shorter than this length", min_query_len)
		("load-threads", 0, "number of CPU threads for file I/O", load_threads, 16)
		("minichunk", 0, "Mini chunk size for file I/O", minichunk, (size_t)262144)
		("seed-cut", 0, "cutoff for seed complexity", seed_cut_)
		("freq-masking", 0, "mask seeds based on frequency", freq_masking)
		("freq-sd", 0, "number of standard deviations for ignoring frequent seeds", freq_sd_, 0.0)
		("sketch-size", 0, "Subsample seeds based on minimizer sketch of the given size", sketch_size)
		("tile-size", 0, "Loop tiling size (default=1024)", tile_size, (uint32_t)1024)
		("id2", 0, "minimum number of identities for stage 1 hit", min_identities_)
		("linsearch", 0, "only consider seed hits against longest target for identical seeds", lin_stage1_target)
		("lin-stage1", 0, "only consider seed hits against longest query for identical seeds", lin_stage1_query)
		("xdrop", 'x', "xdrop for ungapped alignment", ungapped_xdrop, 12.3)
		("reseek-xdrop", 0, "xdrop for ungapped alignment (using --reseek-diags)", reseek_ungapped_xdrop, 20.0)
		("ungapped-evalue", 0, "E-value threshold for ungapped filter (auto)", ungapped_evalue_, -1.0)
		("ungapped-evalue-short", 0, "E-value threshold for ungapped filter (short reads) (auto)", ungapped_evalue_short_, -1.0)
		("short-query-ungapped-bitscore", 0, "Bit score threshold for ungapped alignments for short queries", short_query_ungapped_bitscore, 25.0)
		("gapped-filter-evalue", 0, "E-value threshold for gapped filter (auto)", gapped_filter_evalue_, -1.0)
		("band", 0, "band for dynamic programming computation", padding)
		("shape-mask", 0, "seed shapes", shape_mask)
		("taxdump", 0 , "directory containing NCBI taxdump files", taxdump)
		("reseek-diags", 0, "recompute diagonals prior to chaining", reseek_diags)
		("word-threshold", 0, "word threshold in bits", word_threshold)
		("double-hit-window", 0, "window for double hits", double_hit_window, 40)
		("multiprocessing", 0, "enable distributed-memory parallel processing", multiprocessing)
		("mp-init", 0, "initialize multiprocessing run", mp_init)
		("mp-recover", 0, "enable continuation of interrupted multiprocessing run", mp_recover)
		("mp-query-chunk", 0, "process only a single query chunk as specified", mp_query_chunk, -1)
		("culling-overlap", 0, "minimum range overlap with higher scoring hit to delete a hit (default=50%)", inner_culling_overlap, 50.0)
		("taxon-k", 0, "maximum number of targets to report per species", taxon_k, UINT64_C(0))
		("range-cover", 0, "percentage of query range to be covered for range culling (default=50%)", query_range_cover, 50.0)
		("xml-blord-format", 0, "Use gnl|BL_ORD_ID| style format in XML output", xml_blord_format)
		("sam-query-len", 0, "add the query length to the SAM format (tag ZQ)", sam_qlen_field)
		("stop-match-score", 0, "Set the match score of stop codons against each other.", stop_match_score, 1)		
		("target-indexed", 0, "Enable target-indexed mode", target_indexed)
		("self", 0, "Enable self-alignment", self)
		("no-reorder", 0, "Do not reorder alignment output", no_reorder)
		("daa-build-version", 0, "diamond build version to write to DAA file", daa_build_version)
		("cut-bar", 0, "", cut_bar)
		("check-multi-target", 0, "", check_multi_target)
		("roc-file", 0, "", roc_file)
		("family-map", 0, "", family_map)
		("family-map-query", 0, "", family_map_query)
		("query-parallel-limit", 0, "", query_parallel_limit, 3000000u)
		("log-evalue-scale", 0, "", log_evalue_scale, 1.0 / Math::log(2.0))
		("bootstrap", 0, "", bootstrap)
		("heartbeat", 0, "", heartbeat)
		("mp-self", 0, "", mp_self)
#ifdef WITH_DNA
        ("zdrop", 'z', "zdrop for gapped dna alignment", zdrop, 40)
        ("repetition-cutoff", 0 ,"filter out top FLOAT fraction of repetitive minimizers ",repetitive_cutoff,0.0002)
        ("extension", 0, "extension algorithm (wfa, ksw=default)", dna_extension_string, string("ksw"))
        ("chaining-out", 0, "use chaining without extension", chaining_out)
        ("align-long-reads", 0, "use chaining with extension", align_long_reads)
        ("best-hsp-only", 0, "only show the best HSP for any single query-subject pair", best_hsp_only)
        ("chain-pen-gap-scale", 0, "scaling factor for the chaining gap penalty", chain_pen_gap_scale, 0.8)
        ("chain-pen-skip-scale", 0, "scaling factor for the chaining skip penalty", chain_pen_skip_scale, 0.0)
        ("penalty", 0, "blastn mismatch penalty", mismatch_penalty, -3)
        ("reward", 0, "blastn match reward", match_reward, 2)
        ("chain-align-cutoff", 0, "percentage of chains not mapped/aligned in comparison to the best chaining score", chain_fraction_align_)
        ("min-chain-score", 0, "minimum chaining score to be considered for an alignment", min_chain_score_)
        ("max-overlap-extension", 0, "minimum chaining score to be considered for an alignment", max_overlap_extension_)
        ("zdrop-extension", 0, "zdrop for extension at chain ends", zdrop_extension, 80)
        ("zdrop-global", 0, "zdrop for global extension between anchors", zdrop_global, 150)
        ("band-extension", 0, "min band for extension at chain ends", band_extension, 40)
        ("band-global", 0, "min band for global extension between anchors", band_global, 40)
#endif
		("query-or-subject-cover", 0, "", query_or_target_cover);

	auto& view_align_options = parser.add_group("View/Align options", { view, blastp, blastx });
	view_align_options.add()
		("daa", 'a', "DIAMOND alignment archive (DAA) file", daa_file);

	auto& view_options = parser.add_group("View options", { view });
	view_options.add()
		("forwardonly", 0, "only show alignments of forward strand", forwardonly);

	auto& getseq_options = parser.add_group("Getseq options", { getseq });
	getseq_options.add()
		("seq", 0, "Space-separated list of sequence numbers to display.", seq_no);

	auto& composition_matrix_options = parser.add_group("Composition matrix options", { COMPOSITION_MATRIX });
	composition_matrix_options.add()
		("sample-size", 0, "randomly sample this many distinct sequence pairs", composition_matrix_sample_size);

	auto& deprecated_options = parser.add_group("", { blastp, blastx });
	deprecated_options.add()
		("window", 'w', "window size for local hit search", window)
		("ungapped-score", 0, "minimum alignment score to continue local extension", min_ungapped_score)
		("hit-band", 0, "band for hit verification", hit_band)
		("hit-score", 0, "minimum score to keep a tentative alignment", min_hit_score)
		("gapped-xdrop", 'X', "xdrop for gapped alignment in bits", gapped_xdrop, 20.0)
		("rank-ratio2", 0, "include subjects within this ratio of last hit (stage 2)", rank_ratio2, -1.0)
		("rank-ratio", 0, "include subjects within this ratio of last hit", rank_ratio, -1.0)
		("lambda", 0, "lambda parameter for custom matrix", lambda)
		("K", 0, "K parameter for custom matrix", K)
		("verbose", 'v', "verbose console output", verbose);

#ifdef EXTRA
	auto& hidden_options = parser.add_group("", {});
#else
	auto& hidden_options = parser.add_group("", {}, true);
#endif
	hidden_options.add()
		("match1", 0, "", match_file1)
		("match2", 0, "", match_file2)
		("seed-freq", 0, "maximum seed frequency", max_seed_freq, -15.0)
		("space-penalty", 0, "", space_penalty, 0.5)
		("reverse", 0, "", reverse)
		("neighborhood-score", 0, "", neighborhood_score)
		("seed-weight", 'w', "", seed_weight, 7u)
		("superblock", 0, "", superblock, 128)
		("load-balancing", 0, "", load_balancing, (unsigned)Config::query_parallel)
		("log-query", 0, "", log_query)
		("log-subject", 0, "", log_subject)
		("palign", 0, "", threads_align)
		("score-ratio", 0, "", score_ratio, 0.9)
		("fetch-size", 0, "trace point fetch size", fetch_size, 4096u)
		("target-fetch-size", 0, "number of target sequences to fetch for seed extension", target_fetch_size, 4u)
		("rank-factor", 0, "", rank_factor, -1.0)
		("transcript-len-estimate", 0, "", transcript_len_estimate, 1.0)
		("family-counts", 0, "", family_counts_file)
		("radix-cluster-buffered", 0, "", radix_cluster_buffered)
		("join-split-size", 0, "", join_split_size, 100000u)
		("join-split-key-len", 0, "", join_split_key_len, 17u)
		("radix-bits", 0, "", radix_bits, 8u)
		("join-ht-factor", 0, "", join_ht_factor, 1.3)
		("sort-join", 0, "", sort_join)
		("simple-freq", 0, "", simple_freq)
		("freq-treshold", 0, "", freq_treshold)
		("use-dataset-field", 0, "", use_dataset_field)
		("store-query-quality", 0, "", store_query_quality)
		("swipe-chunk-size", 0, "", swipe_chunk_size, 256u)
		("hard-masked", 0, "", hardmasked)
		("cbs-window", 0, "", cbs_window, 40)
		("no-dict", 0, "", no_dict)
		("upgma-edge-limit", 0, "", upgma_edge_limit, (uint64_t)10000000)
		("tree", 0, "", tree_file)
		("upgma-dist", 0, "", upgma_dist)
		("upgma-input", 0, "", upgma_input)
		("log-extend", 0, "", log_extend)
		("chaining-maxgap", 0, "", chaining_maxgap, 2000)
		("tantan-maxRepeatOffset", 0, "maximum tandem repeat period to consider (50)", tantan_maxRepeatOffset, 15)
		("tantan-ungapped", 0, "use tantan masking in ungapped mode", tantan_ungapped)
		("chaining-range-cover", 0, "", chaining_range_cover, (size_t)8)
		("no-swipe-realign", 0, "", no_swipe_realign)
		("chaining-maxnodes", 0, "", chaining_maxnodes)
		("cutoff-score-8bit", 0, "", cutoff_score_8bit, 240)
		("min-band-overlap", 0, "", min_band_overlap, 0.0)
		("min-realign-overhang", 0, "", min_realign_overhang, 30)
		("ungapped-window", 0, "", ungapped_window, 48)
		("gapped-filter-diag-score", 0, "", gapped_filter_diag_bit_score, 12.0)
		("gapped-filter-window", 0, "", gapped_filter_window, 200)
		("output-hits", 0, "", output_hits)
		("band-bin", 0, "", band_bin, 24)
		("col-bin", 0, "", col_bin, 400)		
		("trace-pt-fetch-size", 0, "", trace_pt_fetch_size, UINT64_C(10000000000))
		("short-query-max-len", 0, "", short_query_max_len, 60)
		("gapped-filter-evalue1", 0, "", gapped_filter_evalue1, 2000.0)
		("ext-yield", 0, "", ext_min_yield)
		("full-sw-len", 0, "", full_sw_len)
		("relaxed-evalue-factor", 0, "", relaxed_evalue_factor, 1.0)
		("type", 0, "", type)
		("raw", 0, "", raw)
		("chaining-len-cap", 0, "", chaining_len_cap, 2.0)
		("chaining-min-nodes", 0, "", chaining_min_nodes, (size_t)200)
		("fast-tsv", 0, "", fast_tsv)
		("target-parallel-verbosity", 0, "", target_parallel_verbosity, UINT_MAX)
		("query-memory", 0, "", query_memory)
		("memory-intervals", 0, "", memory_intervals, (size_t)2)
		("seed-hit-density", 0, "", seedhit_density)
		("chunk-size-multiplier", 0, "", chunk_size_multiplier, (size_t)4)
		("score-drop-factor", 0, "", ranking_score_drop_factor, 0.95)
		("left-most-interval", 0, "", left_most_interval, 32)
		("ranking-cutoff-bitscore", 0, "", ranking_cutoff_bitscore, 25.0)
		("no-forward-fp", 0, "", no_forward_fp)
		("no-ref-masking", 0, "", no_ref_masking)
		("target-bias", 0, "", target_bias)
		("output-fp", 0, "", output_fp)
		("family-cap", 0, "", family_cap)
		("cbs-matrix-scale", 0, "", cbs_matrix_scale, 1)
		("query-count", 0, "", query_count, (size_t)1)
		("cbs-err-tolerance", 0, "", cbs_err_tolerance, 0.00000001)
		("cbs-it-limit", 0, "", cbs_it_limit, 2000)
		("hash_join_swap", 0, "", hash_join_swap)
		("deque_bucket_size", 0, "", deque_bucket_size, (size_t)524288)
		("max-swipe-dp", 0, "", max_swipe_dp, INT64_C(1000000))
		("no-reextend", 0, "", no_reextend)
		("file1", 0, "", file1)
		("file2", 0, "", file2)
		("key2", 0, "", key2)
		("motif-mask-file", 0, "", motif_mask_file)
		("max-motif-len", 0, "", max_motif_len, 30)
		("chaining-stacked-hsp-ratio", 0, "", chaining_stacked_hsp_ratio, 0.5)
		("minimizer-window", 0, "", minimizer_window_)
		("min_task_trace_pts", 0, "", min_task_trace_pts, (int64_t)1024)
		("oid-list", 0, "", oid_list)
		("bootstrap-block", 0, "", bootstrap_block, (int64_t)1000000)
		("centroid-factor", 0, "", centroid_factor, (int64_t)3)
		("timeout", 0, "", timeout)
		("resume", 0, "", resume)
		("target_hard_cap", 0, "", target_hard_cap)
		("mapany", 0, "", mapany)
		("neighbors", 0, "", neighbors)
		("reassign-overlap", 0, "", reassign_overlap, 0.3)
		("reassign-ratio", 0, "", reassign_ratio, 0.5)
		("reassign-max", 0, "", reassign_max)
		("add-self-aln", 0, "", add_self_aln)
		("weighted-gvc", 0, "", weighted_gvc)
		("hamming-ext", 0, "", hamming_ext)
		("diag-filter-id", 0, "", diag_filter_id)
		("diag-filter-cov", 0, "", diag_filter_cov)
		("dbtype", 0, "type of sequences in database file (nucl/prot)", dbstring, string("prot"))
		("cluster-similarity", 0, "Clustering similarity measure (default=\"normalized_bitscore_global\")", cluster_similarity)
		("cluster-threshold", 0, "Threshold for the similarity measure (default=50%)", cluster_threshold)
		("cluster-graph-file", 0, "Filename for dumping the graph or reading the graph if cluster-restart", cluster_graph_file)
		("cluster-restart", 0, "Restart clustering from dumped graph", cluster_restart)
		("mcl-expansion", 0, "MCL expansion coefficient (default=2)", cluster_mcl_expansion, (uint32_t)2)
		("mcl-inflation", 0, "MCL inflation coefficient (default=2.0)", cluster_mcl_inflation, 2.0)
		("mcl-chunk-size", 0, "MCL chunk size per thread (default=100)", cluster_mcl_chunk_size, (uint32_t)1)
		("mcl-max-iterations", 0, "MCL maximum iterations (default=100)", cluster_mcl_max_iter, (uint32_t)100)
		("mcl-sparsity-switch", 0, "MCL switch to sparse matrix computation (default=0.8) ", cluster_mcl_sparsity_switch, 0.8)
		("mcl-nonsymmetric", 0, "Do not symmetrize the transition matrix before clustering", cluster_mcl_nonsymmetric)
		("mcl-stats", 0, "Some stats about the connected components in MCL", cluster_mcl_stats)
		("cluster-algo", 0, "Clustering algorithm (\"mcl\")", cluster_algo)
		("approx-backtrace", 0, "", approx_backtrace)
		("narrow-band-cov", 0, "", narrow_band_cov)
		("narrow-band-factor", 0, "", narrow_band_factor)
		("anchor-window", 0, "", anchor_window, 12)
		("anchor-score", 0, "", anchor_score, 1.0)
		("classic-band", 0, "", classic_band)
		("no_8bit_extension", 0, "", no_8bit_extension)
		("no_chaining_merge_hsps", 0, "", no_chaining_merge_hsps)
		("graph-algo", 0, "", graph_algo, string("gvc"))
		("tsv-read-size", 0, "", tsv_read_size, int64_t(GIGABYTES))
		("max-indirection", 0, "", max_indirection)
		("promiscuous-seed-ratio", 0 , "", promiscuous_seed_ratio, 1000.0);
		
	parser.store(argc, argv, command);

	if (debug_log)
		verbosity = 3;
	else if (quiet)
		verbosity = 0;
	else
		verbosity = 1;

	if (verbosity >= 1 && command != version) {
		ostream& header_out = command == Config::help ? cout : cerr;
		header_out << Const::program_name << " v" << Const::version_string << "." << (unsigned)Const::build_version
			<< " (C) Max Planck Society for the Advancement of Science, Benjamin J. Buchfink, University of Tuebingen" << endl;
		header_out << "Documentation, support and updates available at http://www.diamondsearch.org" << endl;
		header_out << "Please cite: http://dx.doi.org/10.1038/s41592-021-01101-x Nature Methods (2021)" << endl << endl;
	}
	*log_stream << Const::program_name << " v" << Const::version_string << "." << (unsigned)Const::build_version << endl;

    if(argc == 2 && command != version && command != regression_test) {
        if (command != help) {
            parser.print_documentation(command);
        } else {
            parser.print_help();
        }
    }

	if (toppercent.present() && max_target_seqs_.present())
		throw runtime_error("--top and -k/--max-target-seqs are mutually exclusive.");

	if (command == blastx && no_self_hits)
		throw runtime_error("--no-self-hits option is not supported in blastx mode.");

	if (long_reads) {
		query_range_culling = true;
		if (toppercent.blank())
			toppercent = 10.0;
		if (frame_shift == 0)
			frame_shift = 15;
	}

	if (global_ranking_targets > 0 && (query_range_culling || taxon_k || multiprocessing || mp_init || mp_recover || comp_based_stats_.get(Stats::DEFAULT_CBS) >= 2 || frame_shift > 0))
		throw runtime_error("Global ranking is not supported in this mode.");

	if (comp_based_stats_.get(Stats::DEFAULT_CBS) >= Stats::CBS::COUNT)
		throw std::runtime_error("Invalid value for --comp-based-stats. Permitted values: 0, 1, 2, 3, 4, 5, 6.");

	Stats::comp_based_stats = Stats::CBS(comp_based_stats_.get(Stats::DEFAULT_CBS), query_match_distance_threshold, length_ratio_threshold, cbs_angle);

	if (command == blastx && !Stats::CBS::support_translated(comp_based_stats_.get(Stats::DEFAULT_CBS)))
		throw std::runtime_error("This mode of composition based stats is not supported for translated searches.");

    if (check_io) {
		switch (command) {
		case Config::makedb: {
            if (database == "")
                throw std::runtime_error("Missing parameter: database file (--db/-d)");
            if (chunk_size != 0.0)
                throw std::runtime_error(
                        "Invalid option: --block-size/-b. Block size is set for the alignment commands.");
            break;
        }
		case Config::blastp:
		case Config::blastx:
        case Config::blastn:
			if (database == "")
				throw runtime_error("Missing parameter: database file (--db/-d)");
			if (daa_file.length() > 0) {
				if (output_file.length() > 0)
					throw runtime_error("Options --daa and --out cannot be used together.");
				if (output_format.size() > 0 && output_format[0] != "daa")
					throw runtime_error("Invalid parameter: --daa/-a. Output file is specified with the --out/-o parameter.");
				output_file = daa_file;
			}
			if (daa_file.length() > 0 || (output_format.size() > 0 && (output_format[0] == "daa" || output_format[0] == "100"))) {
				if (!compression.empty())
					throw runtime_error("Compression is not supported for DAA format.");
				if (!no_auto_append)
					auto_append_extension(output_file, ".daa");
			}
			break;
		default:
			;
		}

		switch (command) {
		case Config::dbinfo:
		case Config::COMPOSITION_MATRIX:
			if (database == "")
				throw runtime_error("Missing parameter: database file (--db/-d)");
		}
	}

	switch (verbosity) {
	case 0:
		break;
	case 1:
		delete message_stream;
		message_stream = &std::cerr;
		break;
	case 2:
	case 3:
		delete message_stream;
		delete log_stream;
		log_stream = &std::cerr;
		message_stream = &std::cerr;
		break;
	default:
		;
	}

	std::ostringstream invocation;
	join(" ", &argv[0], &argv[argc], invocation);
	*log_stream << invocation.str() << endl;
	this->invocation = invocation.str();

	if (!no_auto_append) {
		if (command == Config::makedb)
			auto_append_extension(database, ".dmnd");
		if (command == Config::view)
			auto_append_extension(daa_file, ".daa");
		if (compression == "1")
			auto_append_extension(output_file, ".gz");
		if (compression == "zstd")
			auto_append_extension(output_file, ".zst");
	}

	
#ifndef NDEBUG
	*log_stream << "Assertions enabled." << endl;
#endif
	set_option(threads_, (int)std::thread::hardware_concurrency());

	switch (command) {
	case Config::makedb:
	case Config::blastp:
	case Config::blastx:
    case Config::blastn:
	case Config::view:
	case Config::cluster:
	case Config::DEEPCLUST:
	case Config::LINCLUST:
	case Config::regression_test:
	case Config::compute_medoids:
	case Config::CLUSTER_REASSIGN:
	case Config::GREEDY_VERTEX_COVER:
	case Config::RECLUSTER:
	case Config::COUNT_DISTINCT:
		if (argc != 2)
			*message_stream << "#CPU threads: " << threads_ << endl;
	default:
		;
	}

	switch (command) {
	case Config::blastp:
    case Config::blastx:
	case Config::benchmark:
	case Config::model_sim:
	case Config::opt:
	case Config::mask:
	case Config::makedb:
	case Config::cluster:
	case Config::DEEPCLUST:
	case Config::LINCLUST:
	case Config::regression_test:
	case Config::compute_medoids:
	case Config::LIST_SEEDS:
	case Config::CLUSTER_REASSIGN:
	case Config::CLUSTER_REALIGN:
	case Config::RECLUSTER:
	case Config::MODEL_SEQS:
	case Config::MAKE_SEED_TABLE:
	case Config::COMPOSITION_MATRIX:
		if (frame_shift != 0 && command == Config::blastp)
			throw runtime_error("Frameshift alignments are only supported for translated searches.");
		if (query_range_culling && frame_shift == 0)
			throw runtime_error("Query range culling is only supported in frameshift alignment mode (option -F).");
		if (matrix_file == "") {
			score_matrix = ScoreMatrix(to_upper_case(matrix), gap_open, gap_extend, frame_shift, stop_match_score, 0, cbs_matrix_scale);
		}
		else {
			if (gap_open == -1 || gap_extend == -1)
				throw runtime_error("Custom scoring matrices require setting the --gapopen and --gapextend options.");
			if (!output_format.empty() && (output_format.front() == "daa" || output_format.front() == "100"))
				throw runtime_error("Custom scoring matrices are not supported for the DAA format.");
			if (comp_based_stats_.get(Stats::DEFAULT_CBS) > 1)
				throw runtime_error("This value for --comp-based-stats is not supported when using a custom scoring matrix.");
			score_matrix = ScoreMatrix(matrix_file, gap_open, gap_extend, stop_match_score, ScoreMatrix::Custom());
		}
		if (argc != 2)
			*message_stream << "Scoring parameters: " << score_matrix << endl;
		Masking::instance = unique_ptr<Masking>(new Masking(score_matrix));
	}

	if (command == Config::blastp || command == Config::blastx || command == Config::blastn || command == Config::benchmark || command == Config::model_sim || command == Config::opt
		|| command == Config::mask || command == Config::cluster || command == Config::compute_medoids || command == Config::regression_test || command == Config::CLUSTER_REASSIGN
		|| command == Config::RECLUSTER || command == Config::DEEPCLUST || command == Config::LINCLUST) {
		if (tmpdir == "")
			tmpdir = extract_dir(output_file);

		raw_ungapped_xdrop = score_matrix.rawscore(ungapped_xdrop);
		reseek_raw_ungapped_xdrop = score_matrix.rawscore(reseek_ungapped_xdrop);
		*log_stream << "CPU features detected: " << SIMD::features() << endl;
	}

	sensitivity = Sensitivity::DEFAULT;
	if (mode_faster) set_sens(Sensitivity::FASTER);
	if (mode_fast) set_sens(Sensitivity::FAST);
	if (mode_mid_sensitive) set_sens(Sensitivity::MID_SENSITIVE);
	if (mode_sensitive) set_sens(Sensitivity::SENSITIVE);
	if (mode_more_sensitive) set_sens(Sensitivity::MORE_SENSITIVE);
	if (mode_very_sensitive) set_sens(Sensitivity::VERY_SENSITIVE);
	if (mode_ultra_sensitive) set_sens(Sensitivity::ULTRA_SENSITIVE);
	if (mode_shapes30x10) set_sens(Sensitivity::SHAPES30x10);
	if (mode_shapes6x10) set_sens(Sensitivity::SHAPES6x10);
	if (mode_linclust_20) set_sens(Sensitivity::LINCLUST_20);

	algo = from_string<Algo>(algo_str);
    dbtype = from_string<SequenceType>(dbstring);
#ifdef WITH_DNA
    dna_extension = from_string<DNAExtensionAlgo>(dna_extension_string);
#endif
	Translator::init(query_gencode);

	oid_title_max = 0;

	if (command == blastx || command == blastn)
		input_value_traits = nucleotide_traits;

	if (query_strands != "both" && query_strands != "minus" && query_strands != "plus")
		throw runtime_error("Invalid value for parameter --strand");

	if (unfmt == "fastq" || alfmt == "fastq")
		store_query_quality = true;
	if (!aligned_file.empty())
		*log_stream << "Aligned file format: " << alfmt << endl;

	if (command == blastx) {
		if (query_file.size() > 2)
			throw runtime_error("A maximum of 2 query files is supported in blastx mode.");
	}
	else if (query_file.size() > 1)
		throw runtime_error("--query/-q has more than one argument.");

	if (target_indexed && lowmem_ != 1)
		throw runtime_error("--target-indexed requires -c1.");

	if (swipe_all) {
		algo = Algo::DOUBLE_INDEXED;
	}

	if (query_range_culling && taxon_k != 0)
		throw runtime_error("--taxon-k is not supported for --range-culling mode.");

	if (multiprocessing && parallel_tmpdir.empty())
		throw runtime_error("--multiprocessing requires setting --parallel-tmpdir");
	
	if (multiprocessing) {
		mkdir(parallel_tmpdir);
	}

	//trace_pt_membuf = memory_limit.empty() ? false : Util::String::interpret_number(memory_limit) > 1024 * (1ll << 30);
	//if (hit_membuf) {
//		trace_pt_membuf = true;
	//}
	trace_pt_membuf = hit_membuf;

	if (command != Config::version) {
		static const std::chrono::time_point<std::chrono::system_clock> release_time = std::chrono::system_clock::from_time_t(1788269526);
		if (std::chrono::system_clock::now() - release_time > std::chrono::hours(180 * 24)) {
			set_color(Color::YELLOW, true);
			cerr << "Warning: This version of DIAMOND is more than 180 days old. It is recommended to always use the latest version." << endl;
			reset_color();
		}
	}

	*log_stream << "TRACE_PT_MEMBUF=" << (trace_pt_membuf ? "ON" : "OFF") << endl;
	*log_stream << "MAX_SHAPE_LEN=" << MAX_SHAPE_LEN;
#ifdef SEQ_MASK
	*log_stream << " SEQ_MASK";
#endif
#ifdef STRICT_BAND
	*log_stream << " STRICT_BAND";
#endif
#ifdef HIT_KEEP_TARGET_ID
	*log_stream << "HIT_KEEP_TARGET_ID" << endl;
#endif
	*log_stream << endl;
}