/**
 * The MIT License (MIT)
 * Copyright (c) 2016-2017 Intel Corporation
 * Copyright (c) 2023 dātma, inc™
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include "variant_operations.h"
#include "genomicsdb_multid_vector_field.h"

template<>
std::string get_zero_value() {
  return "";
}

/*
  Copied from defunct gamgee library
  Remaps data dependent on number of genotypes to the new order of alleles as specified in alleles_LUT
  @param input_data - vector of data values for a given input sample as stored in TileDB
  @param input_call_idx
  @param alleles_LUT LUT mapping alleles from input to merged alleles list
  @param num_merged_alleles
  @param NON_REF_exists
  @param alt_alleles_only flag that determines whether only the ALT alleles will be used or all alleles
  @param remapped_data - data structure in which remapped info will be stored
  @num_calls_with_valid_data - keeps track of how many samples had valid values for given genotype idx
 */
template<class DataType>
void  VariantOperations::remap_data_based_on_alleles(const std::vector<DataType>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT, const unsigned num_merged_alleles, bool NON_REF_exists, bool alt_alleles_only,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, DataType missing_value) {
  //index of NON_REF in merged variant
  const auto merged_non_reference_allele_idx = NON_REF_exists ?
      static_cast<int64_t>(static_cast<int>(num_merged_alleles-1)) : lut_missing_value;
  //index of NON_REF in input sample
  const auto input_non_reference_allele_idx = NON_REF_exists ?
      alleles_LUT.get_input_idx_for_merged(input_call_idx, merged_non_reference_allele_idx) : lut_missing_value;
  //Loop over alleles - only ALT or all alleles (BCF_VL_A or BCF_VL_R)
  unsigned length = alt_alleles_only ? num_merged_alleles-1u: num_merged_alleles;
  for (auto j=0u; j<length; ++j) {
    auto allele_j = alt_alleles_only ?  j+1u : j;
    auto input_j_allele = alleles_LUT.get_input_idx_for_merged(input_call_idx, allele_j);
    if (CombineAllelesLUT::is_missing_value(input_j_allele)) {	//no mapping found for current allele in input gvcf
      if (CombineAllelesLUT::is_missing_value(input_non_reference_allele_idx)) {	//input did not have NON_REF allele
        *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, j))) = (missing_value);
        continue;
      } else //input contains NON_REF allele, use its idx
        input_j_allele = input_non_reference_allele_idx;
    }
    assert(!alt_alleles_only || input_j_allele > 0u);   //if only ALT alleles are used, then input_j_allele must be non-0
    auto input_j = alt_alleles_only ? input_j_allele-1u : input_j_allele;
    //Input data could have been truncated due to missing values - if so, put missing value
    if (static_cast<size_t>(input_j) >= input_data.size())
      *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, j))) = missing_value;
    else {
      *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, j))) =
        input_data[input_j];
      if (is_bcf_valid_value<DataType>(input_data[input_j]))
        ++(num_calls_with_valid_data[j]);
    }
  }
}

template<class DataType>
void  VariantOperations::remap_data_based_on_genotype_haploid(const std::vector<DataType>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, DataType missing_value) {
  //index of NON_REF in merged variant
  const auto merged_non_reference_allele_idx = NON_REF_exists ?
      static_cast<int64_t>(static_cast<int>(num_merged_alleles-1)) : lut_missing_value;
  //index of NON_REF in input sample
  const auto input_non_reference_allele_idx = NON_REF_exists ?
      alleles_LUT.get_input_idx_for_merged(input_call_idx, merged_non_reference_allele_idx) : lut_missing_value;
  //Loop over all possible genotype combinations == #alleles
  for (auto allele_j = 0u; allele_j < num_merged_alleles; ++allele_j) {
    auto input_j_allele = alleles_LUT.get_input_idx_for_merged(input_call_idx, allele_j);
    auto gt_idx = allele_j;
    if (CombineAllelesLUT::is_missing_value(input_j_allele)) {	//no mapping found for current allele in input gvcf
      if (CombineAllelesLUT::is_missing_value(input_non_reference_allele_idx)) {	//input did not have NON_REF allele
        *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, gt_idx))) = (missing_value);
        continue;	//skip to next value of allele_j
      } else //input contains NON_REF allele, use its idx
        input_j_allele = input_non_reference_allele_idx;
    }
    //Input data could have been truncated due to missing values - if so, put missing value
    if (static_cast<size_t>(input_j_allele) >= input_data.size())
      *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, gt_idx))) = (missing_value);
    else {
      auto input_gt_idx = input_j_allele;
      *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, gt_idx))) =
        input_data[input_gt_idx];
      if (is_bcf_valid_value<DataType>(input_data[input_gt_idx]))
        ++(num_calls_with_valid_data[gt_idx]);
    }
  }
}

/*
  Copied from defunct gamgee library
  Remaps data dependent on number of genotypes to the new order of alleles as specified in alleles_LUT
  @param input_data - vector of data values for a given input sample as stored in TileDB
  @param input_call_idx
  @param alleles_LUT LUT mapping alleles from input to merged alleles list
  @param num_merged_alleles
  @param remapped_data - data structure in which remapped info will be stored
  @num_calls_with_valid_data - keeps track of how many samples had valid values for given genotype idx
 */
template<class DataType>
void  VariantOperations::remap_data_based_on_genotype_diploid(const std::vector<DataType>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, DataType missing_value) {
  //index of NON_REF in merged variant
  const auto merged_non_reference_allele_idx = NON_REF_exists ?
      static_cast<int64_t>(static_cast<int>(num_merged_alleles-1)) : lut_missing_value;
  //index of NON_REF in input sample
  const auto input_non_reference_allele_idx = NON_REF_exists ?
      alleles_LUT.get_input_idx_for_merged(input_call_idx, merged_non_reference_allele_idx) : lut_missing_value;
  //Loop over all possible genotype combinations
  for (auto allele_j = 0u; allele_j < num_merged_alleles; ++allele_j) {
    auto input_j_allele = alleles_LUT.get_input_idx_for_merged(input_call_idx, allele_j);
    if (CombineAllelesLUT::is_missing_value(input_j_allele)) {	//no mapping found for current allele in input gvcf
      if (CombineAllelesLUT::is_missing_value(input_non_reference_allele_idx)) {	//input did not have NON_REF allele
        //fill in missing values for all genotypes with allele_j as one component
        for (auto allele_k = allele_j; allele_k < num_merged_alleles; ++allele_k) {
          auto gt_idx = bcf_alleles2gt(allele_j, allele_k);
          *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, gt_idx))) = (missing_value);
        }
        continue;	//skip to next value of allele_j
      } else //input contains NON_REF allele, use its idx
        input_j_allele = input_non_reference_allele_idx;
    }
    for (auto allele_k = allele_j; allele_k < num_merged_alleles; ++allele_k) {
      auto gt_idx = bcf_alleles2gt(allele_j, allele_k);
      auto input_k_allele = alleles_LUT.get_input_idx_for_merged(input_call_idx, allele_k);
      if (CombineAllelesLUT::is_missing_value(input_k_allele)) {	//no mapping found for current allele in input gvcf
        if (CombineAllelesLUT::is_missing_value(input_non_reference_allele_idx)) {	//input did not have NON_REF allele
          *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, gt_idx))) = (missing_value);
          continue;	//skip to next value of allele_k
        } else //input has NON_REF, use its idx
          input_k_allele = input_non_reference_allele_idx;
      }
      auto input_gt_idx = (bcf_alleles2gt(input_j_allele, input_k_allele));
      //Input data could have been truncated due to missing values - if so, put missing value
      if (static_cast<size_t>(input_gt_idx) >= input_data.size())
        *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, gt_idx))) = (missing_value);
      else {
        *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, gt_idx))) =
          input_data[input_gt_idx];
        if (is_bcf_valid_value<DataType>(input_data[input_gt_idx]))
          ++(num_calls_with_valid_data[gt_idx]);
      }
    }
  }
}


/*
 * Reorders fields whose length and order depend on the number of genotypes (BCF_VL_G)
 * for general ploidy
 */
template<class DataType>
void VariantOperations::remap_data_based_on_genotype_general(const std::vector<DataType>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists, const unsigned ploidy,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, DataType missing_value,
    std::vector<int>& remapped_allele_idx_vec_for_current_gt_combination,
    std::vector<std::pair<int, int> >& ploidy_index_allele_index_stack,
    std::vector<int>& input_call_allele_idx_vec_for_current_gt_combination,
    remap_operator_function_type<DataType> op
                                                            ) {
  if (ploidy == 0u)
    return;
  //index of NON_REF in merged variant
  const auto merged_non_reference_allele_idx = NON_REF_exists ?
      static_cast<int64_t>(static_cast<int>(num_merged_alleles-1)) : lut_missing_value;
  //index of NON_REF in input sample
  const auto input_non_reference_allele_idx = NON_REF_exists ?
      alleles_LUT.get_input_idx_for_merged(input_call_idx, merged_non_reference_allele_idx) : lut_missing_value;
#define SET_PLOIDY_INDEX_IN_STACK_ELEMENT(X,Y) ((X).first = (Y))
#define SET_ALLELE_INDEX_IN_STACK_ELEMENT(X,Y) ((X).second = (Y))
#define GET_PLOIDY_INDEX_IN_STACK_ELEMENT(X) ((X).first)
#define GET_ALLELE_INDEX_IN_STACK_ELEMENT(X) ((X).second)
  remapped_allele_idx_vec_for_current_gt_combination.resize(ploidy+1u); //+1 to avoid unnecessary if statements in the while loop
  input_call_allele_idx_vec_for_current_gt_combination.resize(ploidy);
  //Enumerate genotypes based on method described in
  //http://genome.sph.umich.edu/wiki/Relationship_between_Ploidy,_Alleles_and_Genotypes
  //Use custom "stack" instead of a recursive function call
  //"top" of the stack is the last element of the vector
  //resize to max #genotypes - avoids frequent dynamic memory allocations/frees
  ploidy_index_allele_index_stack.resize(KnownFieldInfo::get_number_of_genotypes(num_merged_alleles-1u, ploidy));
  //In each iteration, generate all genotypes where the last ploidy corresponds to the allele
  //corresponding to top_level_allele_idx
  auto allele_idx = 0;
  auto ploidy_idx = 0;
  //In each iteration of the loop, let the top of the stack contain (P=x, A=y)
  //The subsequent iterations will generate all genotype combinations corresponding to
  //gt[x] == y first before moving on to elements in the stack below
  //Initializer element in the stack set (P=ploidy, A=#alleles-1)
  //Thus, the while loop will generate all genotype combinations for ploidies 0..ploidy-1
  //with alleles 0..alleles-1
  SET_PLOIDY_INDEX_IN_STACK_ELEMENT(ploidy_index_allele_index_stack[0u], ploidy);
  SET_ALLELE_INDEX_IN_STACK_ELEMENT(ploidy_index_allele_index_stack[0u], num_merged_alleles-1);
  auto num_elements_in_stack = 1u;
  auto remapped_gt_idx = 0ull;
  while (num_elements_in_stack > 0u) {
    auto& top_stack_element = ploidy_index_allele_index_stack[num_elements_in_stack-1u];
    allele_idx = GET_ALLELE_INDEX_IN_STACK_ELEMENT(top_stack_element);
    ploidy_idx = GET_PLOIDY_INDEX_IN_STACK_ELEMENT(top_stack_element);
    --num_elements_in_stack; //popped stack
    assert(ploidy_idx >= 0 && static_cast<size_t>(ploidy_idx) < remapped_allele_idx_vec_for_current_gt_combination.size());
    remapped_allele_idx_vec_for_current_gt_combination[ploidy_idx] = allele_idx;
    //Assigned one allele idx for all ploidys
    if (ploidy_idx == 0) {
      auto curr_genotype_combination_contains_missing_allele_for_input = false;
      for (auto i=0u; i<ploidy; ++i) {
        auto input_allele_idx = alleles_LUT.get_input_idx_for_merged(input_call_idx, remapped_allele_idx_vec_for_current_gt_combination[i]);
        if (CombineAllelesLUT::is_missing_value(input_allele_idx)) {	//no mapping found for current allele in input gvcf
          input_call_allele_idx_vec_for_current_gt_combination[i] = input_non_reference_allele_idx; //set to NON_REF idx, possibly missing
          curr_genotype_combination_contains_missing_allele_for_input =
            curr_genotype_combination_contains_missing_allele_for_input ||
            CombineAllelesLUT::is_missing_value(input_non_reference_allele_idx);
        } else
          input_call_allele_idx_vec_for_current_gt_combination[i] = input_allele_idx;
      }
      op(input_data,
         input_call_idx,
         alleles_LUT,
         num_merged_alleles, NON_REF_exists,
         curr_genotype_combination_contains_missing_allele_for_input,
         ploidy,
         remapped_data,
         num_calls_with_valid_data, missing_value,
         remapped_allele_idx_vec_for_current_gt_combination,
         remapped_gt_idx,
         input_call_allele_idx_vec_for_current_gt_combination);
      ++remapped_gt_idx;
    } else {
      --ploidy_idx; //current ploidy_idx
      //Reverse order so that alleles with lower idx are closer to the top of the stack
      for (auto i=allele_idx; i>=0; --i) {
        assert(num_elements_in_stack < ploidy_index_allele_index_stack.size());
        auto& curr_stack_element = ploidy_index_allele_index_stack[num_elements_in_stack];
        SET_PLOIDY_INDEX_IN_STACK_ELEMENT(curr_stack_element, ploidy_idx);
        SET_ALLELE_INDEX_IN_STACK_ELEMENT(curr_stack_element, i);
        ++num_elements_in_stack;
      }
    }
  }
}

uint64_t VariantOperations::get_genotype_index(std::vector<int>& allele_idx_vec, const bool is_sorted) {
  switch (allele_idx_vec.size()) { //ploidy
  case 0u:
    return 0u;
  case 1u:
    return allele_idx_vec[0u];
  case 2u:
    return bcf_alleles2gt(allele_idx_vec[0u], allele_idx_vec[1u]);
  default: {
    //To get genotype combination index for the input, alleles must be in sorted order
    if (!is_sorted)
      std::sort(allele_idx_vec.begin(), allele_idx_vec.end());
    auto gt_idx = 0ull;
    //From http://genome.sph.umich.edu/wiki/Relationship_between_Ploidy,_Alleles_and_Genotypes
    for (auto i=0ull; i<allele_idx_vec.size(); ++i)
      gt_idx += VariantOperations::nCr(i+allele_idx_vec[i], allele_idx_vec[i]-1);
    return gt_idx;
  }
  }
}

template<class DataType>
void VariantOperations::reorder_field_based_on_genotype_index(const std::vector<DataType>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists,
    const bool curr_genotype_combination_contains_missing_allele_for_input,
    const unsigned ploidy,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, DataType missing_value,
    const std::vector<int>& remapped_allele_idx_vec_for_current_gt_combination,
    const uint64_t remapped_gt_idx,
    std::vector<int>& input_call_allele_idx_vec_for_current_gt_combination
                                                             ) {
  if (curr_genotype_combination_contains_missing_allele_for_input) { //no genotype in input corresponding to this allele combination
    //Put missing value
    *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, remapped_gt_idx))) = (missing_value);
    return;
  }
  auto input_gt_idx = VariantOperations::get_genotype_index(input_call_allele_idx_vec_for_current_gt_combination, false);
  assert(remapped_gt_idx < KnownFieldInfo::get_number_of_genotypes(num_merged_alleles-1u, ploidy));
  //Input data could have been truncated due to missing values - if so, put missing value
  if (input_gt_idx >= input_data.size())
    *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, remapped_gt_idx))) = (missing_value);
  else {
    *(reinterpret_cast<DataType*>(remapped_data.put_address(input_call_idx, remapped_gt_idx))) =
      input_data[input_gt_idx];
    if (is_bcf_valid_value<DataType>(input_data[input_gt_idx]))
      ++(num_calls_with_valid_data[remapped_gt_idx]);
  }
}

//Wrapper function
template<class DataType>
void  VariantOperations::remap_data_based_on_genotype(const std::vector<DataType>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists, const unsigned ploidy,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, DataType missing_value,
    std::vector<int>& remapped_allele_idx_vec_for_current_gt_combination, std::vector<std::pair<int, int> >& ploidy_index_allele_index_stack,
    std::vector<int>& input_call_allele_idx_vec_for_current_gt_combination) {
  switch (ploidy) {
  case 1u:
    remap_data_based_on_genotype_haploid(input_data,
                                         input_call_idx,
                                         alleles_LUT,
                                         num_merged_alleles, NON_REF_exists,
                                         remapped_data,
                                         num_calls_with_valid_data, missing_value);
    break;
  case 2u:
    remap_data_based_on_genotype_diploid(input_data,
                                         input_call_idx,
                                         alleles_LUT,
                                         num_merged_alleles, NON_REF_exists,
                                         remapped_data,
                                         num_calls_with_valid_data, missing_value);
    break;
  default:  //why not let this case handle diploid and haploid? A. speed, diploid is common case
    remap_data_based_on_genotype_general<DataType>(input_data,
        input_call_idx,
        alleles_LUT,
        num_merged_alleles, NON_REF_exists, ploidy,
        remapped_data,
        num_calls_with_valid_data, missing_value,
        remapped_allele_idx_vec_for_current_gt_combination, ploidy_index_allele_index_stack,
        input_call_allele_idx_vec_for_current_gt_combination,
        VariantOperations::reorder_field_based_on_genotype_index<DataType>);
    break;
  }
}

//genotype corresponding to min value PL field

template<class DataType>
void GenotypeForMinValueTracker<DataType>::track_minimum(const std::vector<DataType>& input_data,
    std::vector<int>& allele_idx_vec_for_current_gt_combination) {
  auto gt_idx = VariantOperations::get_genotype_index(allele_idx_vec_for_current_gt_combination, false);
  if (gt_idx < input_data.size() && is_bcf_valid_value<DataType>(input_data[gt_idx])
      && input_data[gt_idx] < m_current_min_value ) {
    m_current_min_value = input_data[gt_idx];
    m_genotype_index = gt_idx;
    m_allele_idx_vec_for_current_genotype = allele_idx_vec_for_current_gt_combination;
    m_found_one_valid_value = true;
  }
}

template<class DataType>
void GenotypeForMinValueTracker<DataType>::determine_allele_combination_and_genotype_index_for_min_value(const std::vector<DataType>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists,
    const bool curr_genotype_combination_contains_missing_allele_for_input,
    const unsigned ploidy,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, DataType missing_value,
    const std::vector<int>& remapped_allele_idx_vec_for_current_gt_combination,
    const uint64_t remapped_gt_idx,
    std::vector<int>& input_call_allele_idx_vec_for_current_gt_combination
                                                                                                        ) {
  auto& min_tracker = dynamic_cast<GenotypeForMinValueTracker<DataType>&>(remapped_data);
  min_tracker.track_minimum(input_data, input_call_allele_idx_vec_for_current_gt_combination);
}

template<class DataType, class CombineResultType>
GenotypeForMinValueResultTuple VariantFieldHandler<DataType, CombineResultType>::determine_allele_combination_and_genotype_index_for_min_value(
  const std::unique_ptr<VariantFieldBase>& orig_field_ptr,
  unsigned num_merged_alleles, bool non_ref_exists, const unsigned ploidy) {
  m_min_genotype_tracker.reset();
  if (!(orig_field_ptr.get() && orig_field_ptr->is_valid()))
    return m_min_genotype_tracker.get_tuple_for_min_value(); //returns tuple with valid flag set to false
  //Assert that ptr is of type VariantFieldPrimitiveVectorData<DataType>
  assert(dynamic_cast<VariantFieldPrimitiveVectorData<DataType>*>(orig_field_ptr.get()));
  auto& orig_vector_field_data = static_cast<VariantFieldPrimitiveVectorData<DataType>*>(orig_field_ptr.get())->get();
  m_input_call_allele_idx_vec.resize(ploidy);
  auto num_genotypes = KnownFieldInfo::get_num_elements_given_length_descriptor(BCF_VL_G,
                       num_merged_alleles-1u, ploidy, 0u);
  switch (ploidy) {
  case 1u: { //haploid, #genotypes == num_merged_alleles
    for (auto i=0u; i<std::min<unsigned>(num_genotypes, orig_vector_field_data.size()); ++i) {
      m_input_call_allele_idx_vec[0u] = i;
      m_min_genotype_tracker.track_minimum(orig_vector_field_data, m_input_call_allele_idx_vec);
    }
    break;
  }
  case 2u: { //diploid - iterate over possible GT combinations
    for (auto i=0u; i<num_merged_alleles; ++i) {
      m_input_call_allele_idx_vec[0u] = i;
      for (auto j=i; j<num_merged_alleles; ++j) {
        m_input_call_allele_idx_vec[1u] = j;
        m_min_genotype_tracker.track_minimum(orig_vector_field_data, m_input_call_allele_idx_vec);
      }
    }
    break;
  }
  default: { //why not let this case handle diploid and haploid? A. speed, diploid is common case
    //LUT with single sample, num_merged_alleles
    m_alleles_identity_LUT.resize_luts_if_needed(1u, num_merged_alleles);
    m_alleles_identity_LUT.reset_luts();
    std::vector<uint64_t> empty_vec;
    for (auto i=0u; i<num_merged_alleles; ++i)
      m_alleles_identity_LUT.add_input_merged_idx_pair(0u, i, i);
    VariantOperations::remap_data_based_on_genotype_general<DataType>(orig_vector_field_data,
        0ull,
        m_alleles_identity_LUT,
        num_merged_alleles, non_ref_exists, ploidy,
        dynamic_cast<RemappedDataWrapperBase&>(m_min_genotype_tracker),
        empty_vec, m_bcf_missing_value,
        m_allele_idx_vec_for_current_genotype, m_ploidy_index_alleles_index_stack,
        m_input_call_allele_idx_vec,
        GenotypeForMinValueTracker<DataType>::determine_allele_combination_and_genotype_index_for_min_value);
    break;
  }
  }
  return m_min_genotype_tracker.get_tuple_for_min_value(); //returns tuple with valid flag set to false
}

//Variant handler functions
template<class DataType, class CombineResultType>
void VariantFieldHandler<DataType, CombineResultType>::remap_vector_data(const std::unique_ptr<VariantFieldBase>& orig_field_ptr,
    uint64_t curr_call_idx_in_variant,
    const CombineAllelesLUT& alleles_LUT,
    unsigned num_merged_alleles, bool non_ref_exists, const unsigned ploidy,
    const FieldLengthDescriptor& length_descriptor, unsigned num_merged_elements, RemappedVariant& remapper_variant) {
  auto* raw_orig_field_ptr = orig_field_ptr.get();
  if (raw_orig_field_ptr == 0)
    return;
  //Assert that ptr is of type VariantFieldPrimitiveVectorData<DataType>
  assert(dynamic_cast<VariantFieldPrimitiveVectorData<DataType>*>(raw_orig_field_ptr));
  auto* orig_vector_field_ptr = static_cast<VariantFieldPrimitiveVectorData<DataType>*>(raw_orig_field_ptr);
  //Resize and zero vector
  m_num_calls_with_valid_data.resize(num_merged_elements);
  memset(&(m_num_calls_with_valid_data[0]), 0, num_merged_elements*sizeof(uint64_t));
  /*Remap field in copy (through remapper_variant)*/
  if (length_descriptor.is_length_genotype_dependent())
    VariantOperations::remap_data_based_on_genotype<DataType>(
      orig_vector_field_ptr->get(), curr_call_idx_in_variant,
      alleles_LUT,
      num_merged_alleles, non_ref_exists, ploidy,
      remapper_variant, m_num_calls_with_valid_data, m_bcf_missing_value,
      m_allele_idx_vec_for_current_genotype, m_ploidy_index_alleles_index_stack,
      m_input_call_allele_idx_vec);
  else
    VariantOperations::remap_data_based_on_alleles<DataType>(
      orig_vector_field_ptr->get(), curr_call_idx_in_variant,
      alleles_LUT, num_merged_alleles, non_ref_exists,
      length_descriptor.is_length_only_ALT_alleles_dependent(),
      remapper_variant, m_num_calls_with_valid_data, m_bcf_missing_value);
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::get_valid_median(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, void* output_ptr, unsigned& num_valid_elements) {
  m_median_compute_vector.resize(variant.get_num_calls());
  auto valid_idx = 0u;
  //Iterate over valid calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    auto& field_ptr = curr_call.get_field(query_idx);
    //Valid field
    if (field_ptr.get() && field_ptr->is_valid()) {
      //Must always be vector<DataType>
      auto* ptr = dynamic_cast<VariantFieldPrimitiveVectorData<DataType>*>(field_ptr.get());
      assert(ptr);
      assert((ptr->get()).size() > 0u);
      auto val = ptr->get()[0u];
      if (is_bcf_valid_value<DataType>(val))
        m_median_compute_vector[valid_idx++] = val;
    }
  }
  if (valid_idx == 0u)  //no valid fields found
    return false;
  auto mid_point = valid_idx/2u;
  std::nth_element(m_median_compute_vector.begin(), m_median_compute_vector.begin()+mid_point, m_median_compute_vector.begin()+valid_idx);
  auto result_ptr = reinterpret_cast<DataType*>(output_ptr);
  *result_ptr = m_median_compute_vector[mid_point];
  return true;
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::get_valid_sum(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, void* output_ptr, unsigned& num_valid_elements) {
  auto valid_idx = 0u;
  auto is_first_iter = true;
  //Iterate over valid calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    auto is_valid = get_valid_sum(curr_call.get_field(query_idx), is_first_iter);
    valid_idx += (is_valid ? 1u : 0u);
    is_first_iter = false;
  }
  num_valid_elements = valid_idx;
  if (valid_idx == 0u)  //no valid fields found
    return false;
  auto result_ptr = reinterpret_cast<CombineResultType*>(output_ptr);
  *result_ptr = m_sum;
  return true;
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::get_valid_sum(const std::unique_ptr<VariantFieldBase>& field_ptr,
    const bool reset_accumulator) {
  if(reset_accumulator)
    m_sum = get_zero_value<DataType>();
  auto is_valid = false;
  //Valid field
  if (field_ptr.get() && field_ptr->is_valid()) {
    //Must always be vector<DataType>
    const auto* ptr = dynamic_cast<const VariantFieldPrimitiveVectorData<DataType>*>(field_ptr.get());
    assert(ptr);
    assert((ptr->get()).size() > 0u);
    auto val = ptr->get()[0u];
    if (is_bcf_valid_value<DataType>(val)) {
      m_sum += val;
      is_valid = true;
    }
  }
  return is_valid;
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::get_valid_mean(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, void* output_ptr, unsigned& num_valid_elements) {
  auto status = get_valid_sum(variant, query_config, query_idx, output_ptr, num_valid_elements);
  if (status) {
    auto result_ptr = reinterpret_cast<DataType*>(output_ptr);
    *result_ptr = m_sum/num_valid_elements;
  }
  return status;
}

//Mean is undefined for strings
template<>
bool VariantFieldHandler<std::string>::get_valid_mean(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, void* output_ptr, unsigned& num_valid_elements) {
  throw VariantOperationException("Mean is an undefined operation for combining string fields");
  return false;
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::compute_valid_element_wise_sum(
    const std::unique_ptr<VariantFieldBase>& field_ptr,
    const bool reset_accumulator) {
  if(reset_accumulator)
    m_element_wise_operations_result.clear();
  const auto prev_num_elements = m_element_wise_operations_result.size();
  //Valid field
  if (field_ptr.get() && field_ptr->is_valid()) {
    //Must always be vector<DataType>
    const auto* ptr = dynamic_cast<const VariantFieldPrimitiveVectorData<DataType>*>(field_ptr.get());
    assert(ptr);
    const auto& vec = ptr->get();
    if (vec.size() > m_element_wise_operations_result.size())
      m_element_wise_operations_result.resize(vec.size(), get_bcf_missing_value<CombineResultType>());
    const auto min_size = std::min(vec.size(), prev_num_elements);
    for (auto i=0ull; i<min_size; ++i) {
      auto old_value = m_element_wise_operations_result[i];
      auto new_value = vec[i];
      auto is_old_value_valid = is_bcf_valid_value<CombineResultType>(old_value);
      auto is_new_value_valid = is_bcf_valid_value<DataType>(new_value);
      auto updated_val = (is_old_value_valid && is_new_value_valid)
	? old_value + new_value : (is_new_value_valid
	    ? static_cast<CombineResultType>(new_value) : old_value);
      m_element_wise_operations_result[i] = updated_val;
    }
    for(auto i=prev_num_elements;i<vec.size();++i) {
      const auto val = vec[i];
      m_element_wise_operations_result[i] =
	is_bcf_missing_value<DataType>(val) ? get_bcf_missing_value<CombineResultType>()
	: is_bcf_vector_end_value<DataType>(val) ? get_bcf_vector_end_value<CombineResultType>()
	: val;
    }
  }
  return (m_element_wise_operations_result.size() > 0u);
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::compute_valid_element_wise_sum(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, const void** output_ptr, unsigned& num_elements) {
  m_element_wise_operations_result.clear();
  //Iterate over valid calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    compute_valid_element_wise_sum(curr_call.get_field(query_idx));
  }
  (*output_ptr) = &(m_element_wise_operations_result[0]);
  num_elements = m_element_wise_operations_result.size();
  return (m_element_wise_operations_result.size() > 0u);
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::compute_valid_element_wise_sum_2D_vector(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx) {
  auto num_valid_elements = 0ull;
  assert(query_config.get_length_descriptor_for_query_attribute_idx(query_idx).get_num_dimensions() == 2u);
  auto vid_field_info = query_config.get_field_info_for_query_attribute_idx(query_idx);
  assert(vid_field_info->get_genomicsdb_type().get_tuple_element_type_index(0u) == std::type_index(typeid(DataType)));
  m_2D_element_wise_operations_result.clear();
  //Iterate over valid calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    auto is_valid = compute_valid_element_wise_sum_2D_vector(curr_call.get_field(query_idx), *vid_field_info);
    num_valid_elements += (is_valid ? 1u : 0u);
  }
  return (num_valid_elements > 0u);
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::compute_valid_element_wise_sum_2D_vector(
    const std::unique_ptr<VariantFieldBase>& field_ptr, const FieldInfo& vid_field_info,
    const bool reset_accumulator) {
  assert(vid_field_info.get_genomicsdb_type().get_tuple_element_type_index(0u) == std::type_index(typeid(DataType)));
  if(reset_accumulator)
    m_2D_element_wise_operations_result.clear();
  auto is_valid = false;
  //Valid field
  if (field_ptr.get() && field_ptr->is_valid()) {
    //Must always be vector<uint8_t>
    auto* ptr = dynamic_cast<VariantFieldPrimitiveVectorData<uint8_t, unsigned>*>(field_ptr.get());
    assert(ptr);
    auto& vec = ptr->get();
    GenomicsDBMultiDVectorIdx curr_field_index(&(vec[0u]), &vid_field_info, 0u);
    if (curr_field_index.get_num_entries_in_current_dimension() > m_2D_element_wise_operations_result.size())
      m_2D_element_wise_operations_result.resize(curr_field_index.get_num_entries_in_current_dimension());
    for (auto dim0_idx=0ull; dim0_idx<curr_field_index.get_num_entries_in_current_dimension();
	++dim0_idx) {
      auto num_elements = curr_field_index.get_size_of_current_index()/sizeof(DataType);
      if (num_elements > m_2D_element_wise_operations_result[dim0_idx].size())
	m_2D_element_wise_operations_result[dim0_idx].resize(num_elements, get_bcf_missing_value<CombineResultType>());
      auto data_ptr = curr_field_index.get_ptr<DataType>();
      for (auto i=0ull; i<num_elements; ++i) {
	auto val = data_ptr[i];
	auto old_value = m_2D_element_wise_operations_result[dim0_idx][i];
	auto new_value = data_ptr[i];
	auto is_old_value_valid = is_bcf_valid_value<CombineResultType>(old_value);
	auto is_new_value_valid = is_bcf_valid_value<DataType>(new_value);
	is_valid = is_valid || is_new_value_valid;
	auto updated_val = (is_old_value_valid && is_new_value_valid)
	  ? old_value + new_value : (is_new_value_valid
	      ? static_cast<CombineResultType>(new_value) : old_value);
	m_2D_element_wise_operations_result[dim0_idx][i] = updated_val;
      }
      curr_field_index.advance_index_in_current_dimension();
    }
  }
  return is_valid;
}

template<class DataType, class CombineResultType>
std::string VariantFieldHandler<DataType, CombineResultType>::stringify_2D_vector(const FieldInfo& field_info) {
  auto& length_descriptor = field_info.m_length_descriptor;
  assert(length_descriptor.get_num_dimensions() == 2u);
  auto first_outer_index = true;
  std::stringstream s;
  for (auto i=0ull; i<m_2D_element_wise_operations_result.size(); ++i) {
    if (!first_outer_index)
      s << length_descriptor.get_vcf_delimiter(0u);
    auto first_inner_index = true;
    for (auto j=0ull; j<m_2D_element_wise_operations_result[i].size(); ++j) {
      if (!first_inner_index)
        s << length_descriptor.get_vcf_delimiter(1u);
      const auto val = m_2D_element_wise_operations_result[i][j];
      if (is_bcf_valid_value<CombineResultType>(val))
        s << std::fixed << std::setprecision(3) << val;
      first_inner_index = false;
    }
    first_outer_index = false;
  }
  return s.str();
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::concatenate_field(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, const void** output_ptr, unsigned& num_elements) {
  auto curr_result_size = 0ull;
  //Iterate over valid calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    auto& field_ptr = curr_call.get_field(query_idx);
    //Valid field
    if (field_ptr.get() && field_ptr->is_valid()) {
      //Must always be vector<DataType>
      auto* ptr = dynamic_cast<VariantFieldPrimitiveVectorData<DataType>*>(field_ptr.get());
      assert(ptr);
      auto& vec = ptr->get();
      if (curr_result_size + vec.size() > m_concatenation_result.size())
        m_concatenation_result.resize(curr_result_size+vec.size());
#if !defined(__clang__)
      /* suppress gcc warnings for now:
         src/main/cpp/include/utils/headers.h:56:54: warning: ‘void* memcpy(void*, const void*, size_t)’ writing to an object of type ‘__gnu_cxx::__alloc_traits<std::allocator<std::__cxx11::basic_string<char> >, std::__cxx11::basic_string<char> >::value_type’ {aka ‘class std::__cxx11::basic_string<char>’} with no trivial copy-assignment; use copy-assignment or copy-initialization instead [-Wclass-memaccess] */
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wclass-memaccess"
#endif
      memcpy_s(&(m_concatenation_result[curr_result_size]), vec.size()*sizeof(DataType), &(vec[0]),
	  vec.size()*sizeof(DataType));

#if !defined(__clang__)
#  pragma GCC diagnostic pop
#endif
      curr_result_size += vec.size();
    }
  }
  if (curr_result_size > 0u)
    m_concatenation_result.resize(curr_result_size);
  (*output_ptr) = &(m_concatenation_result[0]);
  num_elements = curr_result_size;
  return (curr_result_size > 0u);
}

//I apologize - really should not have distinguished between vector<non-char> and string [vector<char>] :(
//In the columnar field branch, I hope to remove this unnecessary distinction
template<>
bool VariantFieldHandler<char>::concatenate_field(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, const void** output_ptr, unsigned& num_elements) {
  auto curr_result_size = 0ull;
  //Iterate over valid calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    auto& field_ptr = curr_call.get_field(query_idx);
    //Valid field
    if (field_ptr.get() && field_ptr->is_valid()) {
      auto* ptr = dynamic_cast<VariantFieldString*>(field_ptr.get());
      assert(ptr);
      auto& vec = ptr->get();
      if (curr_result_size + vec.size() > m_concatenation_result.size())
        m_concatenation_result.resize(curr_result_size+vec.size());
      memcpy_s(&(m_concatenation_result[curr_result_size]), vec.size()*sizeof(char),
               &(vec[0]), vec.size()*sizeof(char));
      curr_result_size += vec.size();
    }
  }
  if (curr_result_size > 0u)
    m_concatenation_result.resize(curr_result_size);
  (*output_ptr) = &(m_concatenation_result[0]);
  num_elements = curr_result_size;
  return (curr_result_size > 0u);
}

template<class DataType, class CombineResultType>
bool VariantFieldHandler<DataType, CombineResultType>::collect_and_extend_fields(const Variant& variant, const VariantQueryConfig& query_config,
    unsigned query_idx, const void ** output_ptr, uint64_t& num_elements,
    const bool use_missing_values_only_not_vector_end, const bool use_vector_end_only,
    const bool is_GT_field) {
  auto max_elements_per_call = 0u;
  auto valid_idx = 0u;
  //Iterate over valid calls and obtain max fields over all calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    auto& field_ptr = curr_call.get_field(query_idx);
    //Valid field
    if (field_ptr.get() && field_ptr->is_valid()) {
      max_elements_per_call = std::max<unsigned>(max_elements_per_call, field_ptr->length());
      ++valid_idx;
    }
  }
  if (valid_idx == 0u)  //no valid fields found
    return false;
  //Resize the extended field vector
  if (variant.get_num_calls()*max_elements_per_call > m_extended_field_vector.size())
    m_extended_field_vector.resize(variant.get_num_calls()*max_elements_per_call);
  auto extended_field_vector_idx = 0u;
  //Iterate over all calls, invalid calls also
  for (auto call_idx=0ull; call_idx<variant.get_num_calls(); ++call_idx) {
    auto& curr_call = variant.get_call(call_idx);
    auto& field_ptr = curr_call.get_field(query_idx);
    //#elements inserted for this call
    auto num_elements_inserted = 0u;
    //Valid field in a valid call
    if (curr_call.is_valid() && field_ptr.get() && field_ptr->is_valid()) {
      assert(field_ptr->get_raw_pointer());
#if !defined(__clang__)
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wclass-memaccess"
#endif
      memcpy_s(&(m_extended_field_vector[extended_field_vector_idx]), field_ptr->length()*sizeof(DataType),
               field_ptr->get_raw_pointer(), field_ptr->length()*sizeof(DataType));
#if !defined(__clang__)
#  pragma GCC diagnostic pop
#endif
      num_elements_inserted = field_ptr->length();
      extended_field_vector_idx += num_elements_inserted;
    }
    //WARNING: bunch of horrible hacks to deal with VCF spec and its implementations: htslib and htsjdk
    if (num_elements_inserted == 0u) { //no elements inserted for this call, insert missing value first
      //use_vector_end_only - true only for string fields when the Java interface is used
      //use_missing_values_only_not_vector_end - true only when the Java interface is used
      m_extended_field_vector[extended_field_vector_idx] =
        (use_vector_end_only || (is_GT_field && !use_missing_values_only_not_vector_end))
        ? get_bcf_vector_end_value<DataType>()
        : ((is_GT_field && use_missing_values_only_not_vector_end)
           ? get_bcf_gt_no_call_allele_index<DataType>()  //why? htsjdk does not handle a record where GT is missing in 1 sample, present in another. So, set GT value to no-call
           : get_bcf_missing_value<DataType>());
      ++num_elements_inserted;
      ++extended_field_vector_idx;
    }
    //Pad with vector end values, handles invalid fields also
    //Except when producing records for htsjdk BCF2 - htsjdk has no support for vector end values
    auto padded_value = use_missing_values_only_not_vector_end ? get_bcf_missing_value<DataType>()
                        : get_bcf_vector_end_value<DataType>();
    for (; num_elements_inserted<max_elements_per_call; ++num_elements_inserted,++extended_field_vector_idx)
      m_extended_field_vector[extended_field_vector_idx] = padded_value;
  }
  assert(extended_field_vector_idx <= m_extended_field_vector.size());
  *output_ptr = reinterpret_cast<const void*>(&(m_extended_field_vector[0]));
  num_elements = extended_field_vector_idx;
  return true;
}

template<class DataType1, class DataType2, class CombineResultType>
bool HistogramFieldHandler<DataType1, DataType2, CombineResultType>::compute_valid_histogram_sum_2D_vector(
    const std::unique_ptr<VariantFieldBase>& field_ptr_bin,
    const std::unique_ptr<VariantFieldBase>& field_ptr_count,
    const FieldInfo* vid_field_info_bin,
    const FieldInfo* vid_field_info_count,
    const bool reset_accumulator) {
  if(reset_accumulator)
    m_histogram_map_vec.clear();
  return HistogramFieldHandlerBase::
    compute_valid_histogram_sum_2D_vector<DataType1, DataType2, CombineResultType>(
      field_ptr_bin,
      field_ptr_count,
      vid_field_info_bin,
      vid_field_info_count,
      m_histogram_map_vec);
}

template<class T1, class T2, class CombineResultType>
bool HistogramFieldHandlerBase::compute_valid_histogram_sum_2D_vector(
    const std::unique_ptr<VariantFieldBase>& field_ptr_bin,
    const std::unique_ptr<VariantFieldBase>& field_ptr_count,
    const FieldInfo* vid_field_info_bin,
    const FieldInfo* vid_field_info_count,
    std::vector<std::map<T1, CombineResultType>>& histogram_map_vec) {
  // TODO: not sure how num_valid_elements was meant to be used, until then leave this out.
#if 0
  auto num_valid_elements = 0ull;
#endif
  auto num_calls_with_field = 0ull;
  assert(vid_field_info_bin->get_genomicsdb_type().get_tuple_element_type_index(0u) == std::type_index(typeid(T1)));
  assert(vid_field_info_count->get_genomicsdb_type().get_tuple_element_type_index(0u) == std::type_index(typeid(T2)));
  //Either both valid or both invalid
  assert((field_ptr_bin.get() && field_ptr_bin->is_valid()
	&& field_ptr_count.get() && field_ptr_count->is_valid())
      || (!(field_ptr_bin.get() && field_ptr_bin->is_valid())
	&& !(field_ptr_count.get() && field_ptr_count->is_valid())
	)
      );
  //Valid field
  if (field_ptr_bin.get() && field_ptr_bin->is_valid()) {
    //Must always be vector<uint8_t>
    auto* cast_ptr_bin = dynamic_cast<VariantFieldPrimitiveVectorData<uint8_t, unsigned>*>(field_ptr_bin.get());
    assert(cast_ptr_bin);
    auto* cast_ptr_count = dynamic_cast<VariantFieldPrimitiveVectorData<uint8_t, unsigned>*>(field_ptr_count.get());
    assert(cast_ptr_count);
    GenomicsDBMultiDVectorIdx index_bin(&(cast_ptr_bin->get()[0u]), vid_field_info_bin, 0u);
    GenomicsDBMultiDVectorIdx index_count(&(cast_ptr_count->get()[0u]), vid_field_info_count, 0u);
    assert(index_bin.get_num_entries_in_current_dimension() == index_count.get_num_entries_in_current_dimension());
    if (index_bin.get_num_entries_in_current_dimension() > histogram_map_vec.size())
      histogram_map_vec.resize(index_bin.get_num_entries_in_current_dimension());
    for (auto dim0_idx=0ull; dim0_idx<index_bin.get_num_entries_in_current_dimension();
	++dim0_idx) {
      auto num_elements = index_bin.get_size_of_current_index()/sizeof(T1);
      assert(num_elements == index_count.get_size_of_current_index()/sizeof(T2));
      auto data_ptr_bin = index_bin.get_ptr<T1>();
      auto data_ptr_count = index_count.get_ptr<T2>();
      auto& histogram_map = histogram_map_vec[dim0_idx];
      for (auto i=0ull; i<num_elements; ++i) {
	auto val_bin = data_ptr_bin[i];
	auto val_count = data_ptr_count[i];
	if (is_bcf_valid_value<T1>(val_bin) && is_bcf_valid_value<T2>(val_count)) {
	  auto iter_flag_pair = histogram_map.insert(std::pair<T1, CombineResultType>(val_bin, val_count));
	  //Existing key
	  if (!(iter_flag_pair.second)) {
            (*(iter_flag_pair.first)).second += val_count;
          }
#if 0
	  ++num_valid_elements;
#endif
	}
      }
      index_bin.advance_index_in_current_dimension();
      index_count.advance_index_in_current_dimension();
    }
    ++num_calls_with_field;
  }
  if (num_calls_with_field == 0u)
    return false;
  return true;
}

template<class T1, class T2, class CombineResultType>
bool HistogramFieldHandlerBase::compute_valid_histogram_sum_2D_vector_and_stringify(const Variant& variant,
    const VariantQueryConfig& query_config,
    const unsigned query_idx_bin, const unsigned query_idx_count, std::string& result_str) {
  assert(query_config.get_length_descriptor_for_query_attribute_idx(query_idx_bin).get_num_dimensions() == 2u);
  assert(query_config.get_length_descriptor_for_query_attribute_idx(query_idx_count).get_num_dimensions() == 2u);
  auto vid_field_info_bin = query_config.get_field_info_for_query_attribute_idx(query_idx_bin);
  auto vid_field_info_count = query_config.get_field_info_for_query_attribute_idx(query_idx_count);
  assert(vid_field_info_bin->get_genomicsdb_type().get_tuple_element_type_index(0u) == std::type_index(typeid(T1)));
  assert(vid_field_info_count->get_genomicsdb_type().get_tuple_element_type_index(0u) == std::type_index(typeid(T2)));
  std::vector<std::map<T1, CombineResultType>> histogram_map_vec;
  auto contains_one_valid_data = false;
  //Iterate over valid calls
  for (auto iter=variant.begin(), end_iter = variant.end(); iter != end_iter; ++iter) {
    auto& curr_call = *iter;
    auto& field_ptr_bin = curr_call.get_field(query_idx_bin);
    auto& field_ptr_count = curr_call.get_field(query_idx_count);
    contains_one_valid_data = compute_valid_histogram_sum_2D_vector<T1, CombineResultType>(
	field_ptr_bin, field_ptr_count,
	vid_field_info_bin, vid_field_info_count,
	histogram_map_vec) || contains_one_valid_data;
  }
  if (!contains_one_valid_data)
    return false;
  auto& length_descriptor = vid_field_info_bin->m_length_descriptor;
  assert(length_descriptor.get_num_dimensions() == 2u);
  result_str = std::move(HistogramFieldHandlerBase::stringify_histogram<T1, T2, CombineResultType>(histogram_map_vec,
	length_descriptor.get_vcf_delimiter(0u), length_descriptor.get_vcf_delimiter(1u)));
  return true;
}

template<class T1, class T2, class CombineResultType>
std::string HistogramFieldHandlerBase::stringify_histogram(
    const std::vector<std::map<T1, CombineResultType>>& histogram_map_vec,
    const char delim1, const char delim2) {
  std::stringstream s;
  auto first_outer_index = true;
  for (auto i=0ull; i<histogram_map_vec.size(); ++i) {
    if (!first_outer_index)
      s << delim1;
    auto& histogram_map = histogram_map_vec[i];
    auto first_inner_index = true;
    for (auto& pair : histogram_map) {
      if (!first_inner_index)
	s << delim2;
      s << std::fixed << std::setprecision(3) << pair.first << delim2 << pair.second;
      first_inner_index = false;
    }
    first_outer_index = false;
  }
  return s.str();
}

template<class DataType1, class DataType2, class CombineResultType>
std::string HistogramFieldHandler<DataType1, DataType2, CombineResultType>::stringify_histogram(
    const char delim1, const char delim2) const {
  return HistogramFieldHandlerBase::stringify_histogram<DataType1, DataType2, CombineResultType>(
      m_histogram_map_vec, delim1, delim2);
}

//Explicit template instantiation
template class VariantFieldHandler<int>;
template class VariantFieldHandler<int, int64_t>;
template class VariantFieldHandler<unsigned>;
template class VariantFieldHandler<unsigned, uint64_t>;
template class VariantFieldHandler<int64_t>;
template class VariantFieldHandler<uint64_t>;
template class VariantFieldHandler<float>;
template class VariantFieldHandler<double>;
template class VariantFieldHandler<std::string>;
template class VariantFieldHandler<char>;

template
bool HistogramFieldHandlerBase::compute_valid_histogram_sum_2D_vector_and_stringify<int, int, int64_t>(
    const Variant& variant,
    const VariantQueryConfig& query_config,
    const unsigned query_idx_bin, const unsigned query_idx_count, std::string& result_str);
template
bool HistogramFieldHandlerBase::compute_valid_histogram_sum_2D_vector_and_stringify<int, float>(const Variant& variant,
    const VariantQueryConfig& query_config,
    const unsigned query_idx_bin, const unsigned query_idx_count, std::string& result_str);
template
bool HistogramFieldHandlerBase::compute_valid_histogram_sum_2D_vector_and_stringify<float, int, int64_t>(
    const Variant& variant,
    const VariantQueryConfig& query_config,
    const unsigned query_idx_bin, const unsigned query_idx_count, std::string& result_str);
template
bool HistogramFieldHandlerBase::compute_valid_histogram_sum_2D_vector_and_stringify<float, float>(const Variant& variant,
    const VariantQueryConfig& query_config,
    const unsigned query_idx_bin, const unsigned query_idx_count, std::string& result_str);

template class HistogramFieldHandler<int, int, int64_t>;
template class HistogramFieldHandler<int, float>;
template class HistogramFieldHandler<float, int, int64_t>;
template class HistogramFieldHandler<float, float>;

//The following function is called in variant_operations.cc also. For some compilers, explicit instantiation
//of VariantFieldHandler is insufficient. The following function must also be instantiated, else weird link time
//errors are seen for variant_operations.cc. I'm guessing there is some inline error, but cannot be sure.
template
void VariantOperations::remap_data_based_on_genotype(const std::vector<int>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists, const unsigned ploidy,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, int missing_value,
    std::vector<int>& remapped_allele_idx_vec_for_current_gt_combination, std::vector<std::pair<int, int> >& ploidy_index_allele_index_stack,
    std::vector<int>& input_call_allele_idx_vec_for_current_gt_combination);
//Same reason - vcfdiff.cc
template
void VariantOperations::remap_data_based_on_genotype_general(const std::vector<int>& input_data,
    const uint64_t input_call_idx,
    const CombineAllelesLUT& alleles_LUT,
    const unsigned num_merged_alleles, bool NON_REF_exists, const unsigned ploidy,
    RemappedDataWrapperBase& remapped_data,
    std::vector<uint64_t>& num_calls_with_valid_data, int missing_value,
    std::vector<int>& remapped_allele_idx_vec_for_current_gt_combination,
    std::vector<std::pair<int, int> >& ploidy_index_allele_index_stack,
    std::vector<int>& input_call_allele_idx_vec_for_current_gt_combination,
    remap_operator_function_type<int> op
                                                                              );
