using MIToS.Utils

# Mappings
# ========

function _fill_aln_seq_ann!(aln, seq_ann::Vector{ASCIIString}, seq::Vector{UInt8},
                            init::Int, nres::Int, i)
    if length(seq) != nres
        throw(ErrorException(string("There is and aligned sequence with different number of columns [ ",
                                    length(seq), " != ", nres, " ]:\n", ascii(seq))))
    end
    @inbounds for j in 1:nres
        res = seq[j]
        aln[j,i] = Residue( res )
        if res != UInt8('-') && res != UInt8('.')
            seq_ann[j] = string(init)
            init += 1
        else
            seq_ann[j] = ""
        end
    end
    join(seq_ann, ',')
end

function _to_msa_mapping(sequences::Array{ASCIIString,1})
    nseq = size(sequences,1)
    nres = length(sequences[1])
    aln = Array(Residue,nres,nseq)
    mapp = Array(ASCIIString, nseq)
    seq_ann = Array(ASCIIString, nres)
    for i in 1:nseq
        mapp[i] = _fill_aln_seq_ann!(aln, seq_ann, sequences[i].data, 1, nres, i)
    end
    (aln', mapp)
end

function _to_msa_mapping(sequences::Array{ASCIIString,1}, ids::Array{ASCIIString,1})
    nseq = size(sequences,1)
    nres = length(sequences[1])
    aln = Array(Residue,nres,nseq)
    mapp = Array(ASCIIString, nseq)
    seq_ann = Array(ASCIIString, nres)
    sep = r"/|-"
    for i in 1:nseq
        fields = split(ids[i],sep)
        init = length(fields) == 3 ? parse(Int, fields[2]) : 1
        mapp[i] = _fill_aln_seq_ann!(aln, seq_ann, sequences[i].data, init, nres, i)
    end
    (aln', mapp)
end

# Delete Full of Gap Columns
# ==========================

"""
Deletes columns with 100% gaps, this columns are generated by inserts.
"""
function deletefullgapcolumns!(msa::AbstractMultipleSequenceAlignment, annotate::Bool=true)
    mask = columngapfraction(msa) .!= one(Float64)
    number = sum(~mask)
    if number != 0
        annotate && annotate_modification!(msa, string("deletefullgaps!  :  Deletes ", number," columns full of gaps (inserts generate full gap columns on MIToS because lowercase and dots are not allowed)"))
        filtercolumns!(msa, mask, annotate)
    end
    msa
end

function deletefullgapcolumns(msa::Matrix{Residue})
    mask = columngapfraction(msa) .!= one(Float64)
    number = sum(~mask)
    if number != 0
        return(filtercolumns(msa, mask))
    end
    msa
end

@doc """
`parse(io::Union{IO, AbstractString}, format[, output; generatemapping::Bool=false, useidcoordinates::Bool=false, deletefullgaps::Bool=true ])`

The keyword argument `generatemapping` (`false` by default) indicates if the mapping of the sequences ("SeqMap") and columns ("ColMap")
and the number of columns in the original MSA ("NCol") should be generated and saved in the annotations.
If `useidcoordinates` is `true` (default: `false`) the sequence IDs of the form "ID/start-end" are parsed and used for determining the start and end positions when the mappings are generated.
`deletefullgaps` (`true` by default) indicates if columns 100% gaps (generally inserts from a HMM) must be removed from the MSA.
By default, the ambiguous or not standard residues are replaced by gaps (i.e. `J` for leucine or isoleucine).
But, if the keyword argument `checkalphabet` is `true` (`false` by default), the sequences with residues that do not belong to the defined alphabet are deleted.""" parse

# Keepinserts
# ===========

"""
Function to keep insert columns in `parse`.
It uses the first sequence to generate the "Aligned" annotation,
and after that, convert all the characters to uppercase.
"""
function _keepinserts!(SEQS, annot)
    aligned = map(SEQS[1]) do char
        isupper(char) || char == '-' ? '1' : '0'
    end
    setannotcolumn!(annot, "Aligned", aligned)
    map!(uppercase, SEQS)
end

# Delete sequences with ambiguous or not standard residues
# ========================================================

const _alphabet = "ARNDCQEGHILKMFPSTWYV-."

function _checkalphabet(seqs::Vector{ASCIIString})
    N = length(seqs)
    use = trues(N)
    for i in 1:N
        @inbounds seq = seqs[i]
        for char in seq
            if !( uppercase(char) in _alphabet )
                @inbounds use[i] = false
                break
            end
        end
    end
    use
end

function deletenotalphabetsequences!(msa::AbstractMultipleSequenceAlignment, seqs::Vector{ASCIIString}, annotate::Bool=true)
    mask = _checkalphabet(seqs)
    number = sum(~mask)
    if number != 0
        annotate && annotate_modification!(msa, string("deletenotalphabetsequences!  :  Deletes ", number," sequences with ambiguous or not standard residues (Alphabet: ", _alphabet, " )"))
        filtersequences!(msa, mask, annotate)
    end
    msa
end

function deletenotalphabetsequences(msa::Matrix{Residue}, seqs::Vector{ASCIIString})
    mask = _checkalphabet(seqs)
    number = sum(~mask)
    if number != 0
        return(filtersequences(msa, mask))
    end
    msa
end

function _strings_to_msa(seqs::Vector{ASCIIString}, deletefullgaps::Bool, checkalphabet::Bool)
    msa = convert(Matrix{Residue}, seqs)
    if deletefullgaps && (!checkalphabet)
        return( deletefullgapcolumns(msa) )
    elseif deletefullgaps && checkalphabet
        return( deletefullgapcolumns( deletenotalphabetsequences(msa, seqs) ) )
    elseif (!deletefullgaps) && checkalphabet
        return( deletenotalphabetsequences(msa, seqs) )
    else
        return(msa)
    end
end
