#include "../PbbamInternalConfig.h"

#include <pbbam/vcf/VcfHeader.h>

#include <cassert>

#include <type_traits>

#include <pbbam/vcf/VcfFormat.h>

namespace PacBio {
namespace VCF {

static_assert(std::is_copy_constructible<VcfHeader>::value,
              "VcfHeader(const VcfHeader&) is not = default");
static_assert(std::is_copy_assignable<VcfHeader>::value,
              "VcfHeader& operator=(const VcfHeader&) is not = default");

static_assert(std::is_nothrow_move_constructible<VcfHeader>::value,
              "VcfHeader(VcfHeader&&) is not = noexcept");
static_assert(std::is_nothrow_move_assignable<VcfHeader>::value,
              "VcfHeader& operator=(VcfHeader&&) is not = noexcept");

VcfHeader::VcfHeader() { Version(VcfFormat::CurrentVersion()); }

VcfHeader::VcfHeader(const std::string& hdrText) { *this = VcfFormat::ParsedHeader(hdrText); }

VcfHeader& VcfHeader::AddContigDefinition(VCF::ContigDefinition contig)
{
    const auto found = contigLookup_.find(contig.Id());
    if (found == contigLookup_.cend()) {
        contigLookup_.insert({contig.Id(), contigDefinitions_.size()});
        contigDefinitions_.push_back(std::move(contig));
    } else
        contigDefinitions_.at(found->second) = std::move(contig);
    return *this;
}

VcfHeader& VcfHeader::AddFilterDefinition(VCF::FilterDefinition filter)
{
    const auto found = filterLookup_.find(filter.Id());
    if (found == filterLookup_.cend()) {
        filterLookup_.insert({filter.Id(), filterDefinitions_.size()});
        filterDefinitions_.push_back(std::move(filter));
    } else
        filterDefinitions_.at(found->second) = std::move(filter);
    return *this;
}

VcfHeader& VcfHeader::AddFormatDefinition(VCF::FormatDefinition format)
{
    const auto found = formatLookup_.find(format.Id());
    if (found == formatLookup_.cend()) {
        formatLookup_.insert({format.Id(), formatDefinitions_.size()});
        formatDefinitions_.push_back(std::move(format));
    } else
        formatDefinitions_.at(found->second) = std::move(format);
    return *this;
}

VcfHeader& VcfHeader::AddGeneralDefinition(VCF::GeneralDefinition def)
{
    const auto found = generalLookup_.find(def.Id());
    if (found == generalLookup_.cend()) {
        generalLookup_.insert({def.Id(), generalDefinitions_.size()});
        generalDefinitions_.push_back(std::move(def));
    } else
        generalDefinitions_.at(found->second) = std::move(def);
    return *this;
}

VcfHeader& VcfHeader::AddInfoDefinition(VCF::InfoDefinition info)
{
    const auto found = infoLookup_.find(info.Id());
    if (found == infoLookup_.cend()) {
        infoLookup_.insert({info.Id(), infoDefinitions_.size()});
        infoDefinitions_.push_back(std::move(info));
    } else
        infoDefinitions_.at(found->second) = std::move(info);
    return *this;
}

VcfHeader& VcfHeader::AddSample(std::string sample)
{
    const auto found = sampleLookup_.find(sample);
    if (found == sampleLookup_.cend()) {
        sampleLookup_.insert({sample, samples_.size()});
        samples_.push_back(std::move(sample));
    } else
        samples_.at(found->second) = std::move(sample);
    return *this;
}

const std::vector<VCF::ContigDefinition>& VcfHeader::ContigDefinitions() const
{
    return contigDefinitions_;
}

const VCF::ContigDefinition& VcfHeader::ContigDefinition(const std::string& id) const
{
    return contigDefinitions_.at(contigLookup_.at(id));
}

VcfHeader& VcfHeader::ContigDefinitions(std::vector<VCF::ContigDefinition> defs)
{
    contigDefinitions_.clear();
    contigLookup_.clear();
    for (auto&& def : defs)
        AddContigDefinition(std::move(def));
    return *this;
}

const std::string& VcfHeader::FileDate() const
{
    return generalDefinitions_.at(generalLookup_.at("fileDate")).Text();
}

VcfHeader& VcfHeader::FileDate(std::string fileDate)
{
    AddGeneralDefinition({"fileDate", std::move(fileDate)});
    return *this;
}

const std::vector<VCF::FilterDefinition>& VcfHeader::FilterDefinitions() const
{
    return filterDefinitions_;
}

const VCF::FilterDefinition& VcfHeader::FilterDefinition(const std::string& id) const
{
    return filterDefinitions_.at(filterLookup_.at(id));
}

VcfHeader& VcfHeader::FilterDefinitions(std::vector<VCF::FilterDefinition> defs)
{
    filterDefinitions_.clear();
    filterLookup_.clear();
    for (auto&& def : defs)
        AddFilterDefinition(std::move(def));
    return *this;
}

const std::vector<VCF::FormatDefinition>& VcfHeader::FormatDefinitions() const
{
    return formatDefinitions_;
}

const VCF::FormatDefinition& VcfHeader::FormatDefinition(const std::string& id) const
{
    return formatDefinitions_.at(formatLookup_.at(id));
}

VcfHeader& VcfHeader::FormatDefinitions(std::vector<VCF::FormatDefinition> defs)
{
    formatDefinitions_.clear();
    formatLookup_.clear();
    for (auto&& def : defs)
        AddFormatDefinition(std::move(def));
    return *this;
}

const std::vector<VCF::GeneralDefinition>& VcfHeader::GeneralDefinitions() const
{
    return generalDefinitions_;
}

const VCF::GeneralDefinition& VcfHeader::GeneralDefinition(const std::string& id) const
{
    return generalDefinitions_.at(generalLookup_.at(id));
}

VcfHeader& VcfHeader::GeneralDefinitions(std::vector<VCF::GeneralDefinition> defs)
{
    generalDefinitions_.clear();
    generalLookup_.clear();
    for (auto&& def : defs)
        AddGeneralDefinition(std::move(def));
    return *this;
}

const std::vector<VCF::InfoDefinition>& VcfHeader::InfoDefinitions() const
{
    return infoDefinitions_;
}
const VCF::InfoDefinition& VcfHeader::InfoDefinition(const std::string& id) const
{
    return infoDefinitions_.at(infoLookup_.at(id));
}

VcfHeader& VcfHeader::InfoDefinitions(std::vector<VCF::InfoDefinition> defs)
{
    infoDefinitions_.clear();
    infoLookup_.clear();
    for (auto&& def : defs)
        AddInfoDefinition(std::move(def));
    return *this;
}

size_t VcfHeader::NumLines() const
{
    // +1 for #CHROM line
    return generalDefinitions_.size() + contigDefinitions_.size() + infoDefinitions_.size() +
           filterDefinitions_.size() + formatDefinitions_.size() + 1;
}

const Sample& VcfHeader::SampleAt(size_t index) const { return samples_.at(index); }

size_t VcfHeader::IndexOfSample(const Sample& sample) const { return sampleLookup_.at(sample); }

const std::vector<Sample>& VcfHeader::Samples() const { return samples_; }

VcfHeader& VcfHeader::Samples(std::vector<Sample> names)
{
    samples_.clear();
    sampleLookup_.clear();
    for (auto&& name : names)
        AddSample(std::move(name));
    return *this;
}

const std::string& VcfHeader::Version() const
{
    return generalDefinitions_.at(generalLookup_.at("fileformat")).Text();
}

VcfHeader& VcfHeader::Version(std::string version)
{
    AddGeneralDefinition({"fileformat", std::move(version)});
    return *this;
}

}  // namespace VCF
}  // namespace PacBio
