// Copyright (c) 2020 - present Advanced Micro Devices, Inc. All rights reserved.
//
// 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 <boost/scope_exit.hpp>
#include <gtest/gtest.h>
#include <math.h>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>

#include "../client_utils.h"
#include "accuracy_test.h"
#include "fftw_transform.h"
#include "gpubuf.h"
#include "rocfft.h"
#include "rocfft_against_fftw.h"

// Given an array type, return the name as a string.
std::string array_type_name(const rocfft_array_type type)
{
    switch(type)
    {
    case rocfft_array_type_complex_interleaved:
        return "rocfft_array_type_complex_interleaved";
    case rocfft_array_type_complex_planar:
        return "rocfft_array_type_complex_planar";
    case rocfft_array_type_real:
        return "rocfft_array_type_real";
    case rocfft_array_type_hermitian_interleaved:
        return "rocfft_array_type_hermitian_interleaved";
    case rocfft_array_type_hermitian_planar:
        return "rocfft_array_type_hermitian_planar";
    case rocfft_array_type_unset:
        return "rocfft_array_type_unset";
    }
    return "";
}

// Function to return a string with the gpu parameters.
std::string gpu_params(const std::vector<size_t>&    gpu_ilength_cm,
                       const std::vector<size_t>&    gpu_istride_cm,
                       const size_t                  gpu_idist,
                       const std::vector<size_t>&    gpu_ostride_cm,
                       const size_t                  gpu_odist,
                       const size_t                  nbatch,
                       const rocfft_precision        precision,
                       const rocfft_result_placement place,
                       const rocfft_array_type       itype,
                       const rocfft_array_type       otype)
{
    std::stringstream ss;
    ss << "\nGPU params:\n";
    ss << "\tgpu_ilength_cm:";
    for(auto i : gpu_ilength_cm)
        ss << " " << i;
    ss << "\n";
    ss << "\tgpu_istride_cm:";
    for(auto i : gpu_istride_cm)
        ss << " " << i;
    ss << "\n";
    ss << "\tgpu_idist: " << gpu_idist << "\n";

    ss << "\tgpu_ostride_cm:";
    for(auto i : gpu_ostride_cm)
        ss << " " << i;
    ss << "\n";
    ss << "\tgpu_odist: " << gpu_odist << "\n";

    ss << "\tbatch: " << nbatch << "\n";

    if(place == rocfft_placement_inplace)
        ss << "\tin-place\n";
    else
        ss << "\tout-of-place\n";
    ss << "\t" << array_type_name(itype) << " -> " << array_type_name(otype) << "\n";
    if(precision == rocfft_precision_single)
        ss << "\tsingle-precision\n";
    else
        ss << "\tdouble-precision\n";
    return ss.str();
}

// Compute a FFT using rocFFT and compare with the provided CPU reference computation.
void rocfft_transform(const std::vector<size_t>&                                 length,
                      const std::vector<size_t>&                                 istride,
                      const std::vector<size_t>&                                 ostride,
                      const size_t                                               nbatch,
                      const rocfft_precision                                     precision,
                      const rocfft_transform_type                                transformType,
                      const rocfft_array_type                                    itype,
                      const rocfft_array_type                                    otype,
                      const rocfft_result_placement                              place,
                      const std::vector<size_t>&                                 cpu_istride,
                      const std::vector<size_t>&                                 cpu_ostride,
                      const size_t                                               cpu_idist,
                      const size_t                                               cpu_odist,
                      const rocfft_array_type                                    cpu_itype,
                      const rocfft_array_type                                    cpu_otype,
                      const std::vector<std::vector<char, fftwAllocator<char>>>& cpu_input_copy,
                      const std::vector<std::vector<char, fftwAllocator<char>>>& cpu_output,
                      const VectorNorms&                                         cpu_output_norm,
                      std::thread*                                               cpu_output_thread)
{
    // Set up GPU computation:

    if(place == rocfft_placement_inplace)
    {
        const auto stridesize = std::min(istride.size(), ostride.size());
        bool       samestride = true;
        for(int i = 0; i < stridesize; ++i)
        {
            if(istride[i] != ostride[i])
                samestride = false;
        }
        if(!samestride)
        {
            // In-place transforms require identical input and output strides.
            if(verbose)
            {
                std::cout << "istride:";
                for(const auto& i : istride)
                    std::cout << " " << i;
                std::cout << " ostride0:";
                for(const auto& i : ostride)
                    std::cout << " " << i;
                std::cout << " differ; skipped for in-place transforms: skipping test" << std::endl;
            }
            // TODO: mark skipped
            return;
        }
        if((transformType == rocfft_transform_type_real_forward
            || transformType == rocfft_transform_type_real_inverse)
           && (istride[0] != 1 || ostride[0] != 1))
        {
            // In-place real/complex transforms require unit strides.
            if(verbose)
            {
                std::cout << "istride[0]: " << istride[0] << " ostride[0]: " << ostride[0]
                          << " must be unitary for in-place real/complex transforms: skipping test"
                          << std::endl;
            }
            // TODO: mark skipped
            return;
        }

        if((itype == rocfft_array_type_complex_interleaved
            && otype == rocfft_array_type_complex_planar)
           || (itype == rocfft_array_type_complex_planar
               && otype == rocfft_array_type_complex_interleaved))
        {
            if(verbose)
            {
                std::cout << "In-place c2c transforms require identical io types; skipped.\n";
            }
            return;
        }

        if((itype == rocfft_array_type_real && otype == rocfft_array_type_hermitian_planar)
           || (itype == rocfft_array_type_hermitian_planar && otype == rocfft_array_type_real))
        {
            if(verbose)
            {
                std::cout << "In-place real/complex transforms cannot use planar types; skipped.\n";
            }
            return;
        }
    }

    const size_t dim = length.size();

    auto olength = length;
    if(transformType == rocfft_transform_type_real_forward)
        olength[dim - 1] = olength[dim - 1] / 2 + 1;

    auto ilength = length;
    if(transformType == rocfft_transform_type_real_inverse)
        ilength[dim - 1] = ilength[dim - 1] / 2 + 1;

    auto gpu_istride = compute_stride(ilength,
                                      istride,
                                      place == rocfft_placement_inplace
                                          && transformType == rocfft_transform_type_real_forward);

    auto gpu_ostride = compute_stride(olength,
                                      ostride,
                                      place == rocfft_placement_inplace
                                          && transformType == rocfft_transform_type_real_inverse);

    const auto gpu_idist = set_idist(place, transformType, length, gpu_istride);
    const auto gpu_odist = set_odist(place, transformType, length, gpu_ostride);

    rocfft_status fft_status = rocfft_status_success;
    // Transform parameters from row-major to column-major for rocFFT:
    auto gpu_length_cm  = length;
    auto gpu_ilength_cm = ilength;
    auto gpu_olength_cm = olength;
    auto gpu_istride_cm = gpu_istride;
    auto gpu_ostride_cm = gpu_ostride;
    for(int idx = 0; idx < dim / 2; ++idx)
    {
        const auto toidx = dim - idx - 1;
        std::swap(gpu_istride_cm[idx], gpu_istride_cm[toidx]);
        std::swap(gpu_ostride_cm[idx], gpu_ostride_cm[toidx]);
        std::swap(gpu_length_cm[idx], gpu_length_cm[toidx]);
        std::swap(gpu_ilength_cm[idx], gpu_ilength_cm[toidx]);
        std::swap(gpu_olength_cm[idx], gpu_olength_cm[toidx]);
    }

    if(verbose > 1)
    {
        std::cout << gpu_params(gpu_ilength_cm,
                                gpu_istride_cm,
                                gpu_idist,
                                gpu_ostride_cm,
                                gpu_odist,
                                nbatch,
                                precision,
                                place,
                                itype,
                                otype)
                  << std::flush;
    }

    // Create FFT description
    rocfft_plan_description desc = NULL;
    fft_status                   = rocfft_plan_description_create(&desc);
    EXPECT_TRUE(fft_status == rocfft_status_success) << "rocFFT description creation failure";
    const std::vector<size_t> ioffset = {0, 0};
    const std::vector<size_t> ooffset = {0, 0};
    fft_status                        = rocfft_plan_description_set_data_layout(desc,
                                                         itype,
                                                         otype,
                                                         ioffset.data(),
                                                         ooffset.data(),
                                                         gpu_istride_cm.size(),
                                                         gpu_istride_cm.data(),
                                                         gpu_idist,
                                                         gpu_ostride_cm.size(),
                                                         gpu_ostride_cm.data(),
                                                         gpu_odist);
    EXPECT_TRUE(fft_status == rocfft_status_success)
        << "rocFFT data layout failure: " << fft_status;

    // Create the plan
    rocfft_plan gpu_plan = NULL;
    fft_status           = rocfft_plan_create(&gpu_plan,
                                    place,
                                    transformType,
                                    precision,
                                    gpu_length_cm.size(),
                                    gpu_length_cm.data(),
                                    nbatch,
                                    desc);
    EXPECT_TRUE(fft_status == rocfft_status_success) << "rocFFT plan creation failure";

    // Create execution info
    rocfft_execution_info info = NULL;
    fft_status                 = rocfft_execution_info_create(&info);
    EXPECT_TRUE(fft_status == rocfft_status_success) << "rocFFT execution info creation failure";
    size_t workbuffersize = 0;
    fft_status            = rocfft_plan_get_work_buffer_size(gpu_plan, &workbuffersize);
    EXPECT_TRUE(fft_status == rocfft_status_success) << "rocFFT get buffer size get failure";

    // Number of value in input and output variables.
    const size_t isize = nbatch * gpu_idist;
    const size_t osize = nbatch * gpu_odist;

    // Sizes of individual input and output variables
    const size_t isize_t = var_size<size_t>(precision, itype);
    const size_t osize_t = var_size<size_t>(precision, otype);

    // Numbers of input and output buffers:
    const int nibuffer
        = (itype == rocfft_array_type_complex_planar || itype == rocfft_array_type_hermitian_planar)
              ? 2
              : 1;
    const int nobuffer
        = (otype == rocfft_array_type_complex_planar || otype == rocfft_array_type_hermitian_planar)
              ? 2
              : 1;

    // Check if the problem fits on the device; if it doesn't skip it.
    if(!vram_fits_problem(nibuffer * isize * isize_t,
                          (place == rocfft_placement_inplace) ? 0 : nobuffer * osize * osize_t,
                          workbuffersize))
    {
        rocfft_plan_destroy(gpu_plan);
        rocfft_plan_description_destroy(desc);
        rocfft_execution_info_destroy(info);

        if(verbose)
        {
            std::cout << "Problem won't fit on device; skipped\n";
        }
        // TODO: mark as skipped via gtest.
        return;
    }

    hipError_t hip_status = hipSuccess;

    // Allocate work memory and associate with the execution info
    gpubuf wbuffer;
    if(workbuffersize > 0)
    {
        hip_status = wbuffer.alloc(workbuffersize);
        EXPECT_TRUE(hip_status == hipSuccess) << "hipMalloc failure for work buffer";
        fft_status = rocfft_execution_info_set_work_buffer(info, wbuffer.data(), workbuffersize);
        EXPECT_TRUE(fft_status == rocfft_status_success) << "rocFFT set work buffer failure";
    }

    // Formatted input data:
    auto gpu_input = allocate_host_buffer<fftwAllocator<char>>(
        precision, itype, length, gpu_istride, gpu_idist, nbatch);

    // Copy from contiguous_input to input.
    copy_buffers(cpu_input_copy,
                 gpu_input,
                 ilength,
                 nbatch,
                 precision,
                 cpu_itype,
                 cpu_istride,
                 cpu_idist,
                 itype,
                 gpu_istride,
                 gpu_idist);

    if(verbose > 4)
    {
        std::cout << "GPU input:\n";
        printbuffer(precision, itype, gpu_input, ilength, gpu_istride, nbatch, gpu_idist);
    }
    if(verbose > 5)
    {
        std::cout << "flat GPU input:\n";
        printbuffer_flat(precision, itype, gpu_input, gpu_idist);
    }

    // GPU input and output buffers:
    auto                ibuffer_sizes = buffer_sizes(precision, itype, gpu_idist, nbatch);
    std::vector<gpubuf> ibuffer(ibuffer_sizes.size());
    std::vector<void*>  pibuffer(ibuffer_sizes.size());
    for(unsigned int i = 0; i < ibuffer.size(); ++i)
    {
        hip_status = ibuffer[i].alloc(ibuffer_sizes[i]);
        ASSERT_TRUE(hip_status == hipSuccess)
            << "hipMalloc failure for input buffer " << i << " size " << ibuffer_sizes[i]
            << gpu_params(gpu_ilength_cm,
                          gpu_istride_cm,
                          gpu_idist,
                          gpu_ostride_cm,
                          gpu_odist,
                          nbatch,
                          precision,
                          place,
                          itype,
                          otype);
        pibuffer[i] = ibuffer[i].data();
    }

    std::vector<gpubuf>  obuffer_data;
    std::vector<gpubuf>* obuffer = &obuffer_data;
    if(place == rocfft_placement_inplace)
    {
        obuffer = &ibuffer;
    }
    else
    {
        auto obuffer_sizes = buffer_sizes(precision, otype, gpu_odist, nbatch);
        obuffer_data.resize(obuffer_sizes.size());
        for(unsigned int i = 0; i < obuffer_data.size(); ++i)
        {
            hip_status = obuffer_data[i].alloc(obuffer_sizes[i]);
            ASSERT_TRUE(hip_status == hipSuccess)
                << "hipMalloc failure for output buffer " << i << " size " << obuffer_sizes[i]
                << gpu_params(gpu_ilength_cm,
                              gpu_istride_cm,
                              gpu_idist,
                              gpu_ostride_cm,
                              gpu_odist,
                              nbatch,
                              precision,
                              place,
                              itype,
                              otype);
        }
    }
    std::vector<void*> pobuffer(obuffer->size());
    for(unsigned int i = 0; i < obuffer->size(); ++i)
    {
        pobuffer[i] = obuffer->at(i).data();
    }

    // Copy the input data to the GPU:
    for(int idx = 0; idx < gpu_input.size(); ++idx)
    {
        hip_status = hipMemcpy(ibuffer[idx].data(),
                               gpu_input[idx].data(),
                               gpu_input[idx].size(),
                               hipMemcpyHostToDevice);
        EXPECT_TRUE(hip_status == hipSuccess) << "hipMemcpy failure";
    }

    // Execute the transform:
    fft_status = rocfft_execute(gpu_plan, // plan
                                (void**)pibuffer.data(), // in buffers
                                (void**)pobuffer.data(), // out buffers
                                info); // execution info
    EXPECT_TRUE(fft_status == rocfft_status_success) << "rocFFT plan execution failure";

    // Copy the data back to the host:
    auto gpu_output = allocate_host_buffer<fftwAllocator<char>>(
        precision, otype, olength, gpu_ostride, gpu_odist, nbatch);
    for(int idx = 0; idx < gpu_output.size(); ++idx)
    {
        hip_status = hipMemcpy(gpu_output[idx].data(),
                               obuffer->at(idx).data(),
                               gpu_output[idx].size(),
                               hipMemcpyDeviceToHost);
        EXPECT_TRUE(hip_status == hipSuccess) << "hipMemcpy failure";
    }

    if(verbose > 2)
    {
        std::cout << "GPU output:\n";
        printbuffer(precision, otype, gpu_output, olength, gpu_ostride, nbatch, gpu_odist);
    }
    if(verbose > 5)
    {
        std::cout << "flat GPU output:\n";
        printbuffer_flat(precision, otype, gpu_output, gpu_odist);
    }

    // Compute the Linfinity and L2 norm of the GPU output:
    VectorNorms gpu_norm;
    std::thread normthread([&]() {
        gpu_norm = norm(gpu_output, olength, nbatch, precision, otype, gpu_ostride, gpu_odist);
    });
    if(cpu_output_thread && cpu_output_thread->joinable())
        cpu_output_thread->join();

    // Compute the l-infinity and l-2 distance between the CPU and GPU output:
    std::vector<std::pair<size_t, size_t>> linf_failures;
    const auto                             total_length
        = std::accumulate(length.begin(), length.end(), 1, std::multiplies<size_t>());
    const double linf_cutoff = type_epsilon(precision) * cpu_output_norm.l_inf * log2(total_length);
    auto         diff        = distance(cpu_output,
                         gpu_output,
                         olength,
                         nbatch,
                         precision,
                         cpu_otype,
                         cpu_ostride,
                         cpu_odist,
                         otype,
                         gpu_ostride,
                         gpu_odist,
                         linf_failures,
                         linf_cutoff);
    normthread.join();

    if(verbose > 1)
    {
        std::cout << "GPU output Linf norm: " << gpu_norm.l_inf << "\n";
        std::cout << "GPU output L2 norm:   " << gpu_norm.l_2 << "\n";
        std::cout << "GPU linf norm failures:";
        std::sort(linf_failures.begin(), linf_failures.end());
        for(const auto& i : linf_failures)
        {
            std::cout << " (" << i.first << "," << i.second << ")";
        }
        std::cout << std::endl;
    }

    EXPECT_TRUE(std::isfinite(gpu_norm.l_inf)) << gpu_params(gpu_ilength_cm,
                                                             gpu_istride_cm,
                                                             gpu_idist,
                                                             gpu_ostride_cm,
                                                             gpu_odist,
                                                             nbatch,
                                                             precision,
                                                             place,
                                                             itype,
                                                             otype);
    EXPECT_TRUE(std::isfinite(gpu_norm.l_2)) << gpu_params(gpu_ilength_cm,
                                                           gpu_istride_cm,
                                                           gpu_idist,
                                                           gpu_ostride_cm,
                                                           gpu_odist,
                                                           nbatch,
                                                           precision,
                                                           place,
                                                           itype,
                                                           otype);

    if(verbose > 1)
    {
        std::cout << "L2 diff: " << diff.l_2 << "\n";
        std::cout << "Linf diff: " << diff.l_inf << "\n";
    }

    // TODO: handle case where norm is zero?
    EXPECT_TRUE(diff.l_inf < linf_cutoff)
        << "Linf test failed.  Linf:" << diff.l_inf
        << "\tnormalized Linf: " << diff.l_inf / cpu_output_norm.l_inf
        << "\tcutoff: " << linf_cutoff
        << gpu_params(gpu_ilength_cm,
                      gpu_istride_cm,
                      gpu_idist,
                      gpu_ostride_cm,
                      gpu_odist,
                      nbatch,
                      precision,
                      place,
                      itype,
                      otype);

    EXPECT_TRUE(diff.l_2 / cpu_output_norm.l_2 < sqrt(log2(total_length)) * type_epsilon(precision))
        << "L2 test failed. L2: " << diff.l_2
        << "\tnormalized L2: " << diff.l_2 / cpu_output_norm.l_2
        << "\tepsilon: " << sqrt(log2(total_length)) * type_epsilon(precision)
        << gpu_params(gpu_ilength_cm,
                      gpu_istride_cm,
                      gpu_idist,
                      gpu_ostride_cm,
                      gpu_odist,
                      nbatch,
                      precision,
                      place,
                      itype,
                      otype);

    rocfft_plan_destroy(gpu_plan);
    gpu_plan = NULL;
    rocfft_plan_description_destroy(desc);
    desc = NULL;
    rocfft_execution_info_destroy(info);
    info = NULL;
}

// Test for comparison between FFTW and rocFFT.
TEST_P(accuracy_test, vs_fftw)
{
    const std::vector<size_t>                  length        = std::get<0>(GetParam());
    const std::vector<std::vector<size_t>>     istride_range = std::get<1>(GetParam());
    const std::vector<std::vector<size_t>>     ostride_range = std::get<2>(GetParam());
    const std::vector<size_t>                  batch_range   = std::get<3>(GetParam());
    const rocfft_precision                     precision     = std::get<4>(GetParam());
    const rocfft_transform_type                transformType = std::get<5>(GetParam());
    const std::vector<rocfft_result_placement> place_range   = std::get<6>(GetParam());

    // NB: Input data is row-major.

    const size_t dim = length.size();

    // Input cpu parameters:
    auto ilength = length;
    if(transformType == rocfft_transform_type_real_inverse)
        ilength[dim - 1] = ilength[dim - 1] / 2 + 1;
    const auto cpu_istride = compute_stride(ilength);
    const auto cpu_itype   = contiguous_itype(transformType);
    const auto cpu_idist
        = set_idist(rocfft_placement_notinplace, transformType, length, cpu_istride);

    // Output cpu parameters:
    auto olength = length;
    if(transformType == rocfft_transform_type_real_forward)
        olength[dim - 1] = olength[dim - 1] / 2 + 1;
    const auto cpu_ostride = compute_stride(olength);
    const auto cpu_odist
        = set_odist(rocfft_placement_notinplace, transformType, length, cpu_ostride);
    auto cpu_otype = contiguous_otype(transformType);
    if(verbose > 3)
    {
        std::cout << "CPU  params:\n";
        std::cout << "\tilength:";
        for(auto i : ilength)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tcpu_istride:";
        for(auto i : cpu_istride)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tcpu_idist: " << cpu_idist << std::endl;

        std::cout << "\tolength:";
        for(auto i : olength)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tcpu_ostride:";
        for(auto i : cpu_ostride)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tcpu_odist: " << cpu_odist << std::endl;
    }

    const size_t nbatch = *std::max_element(batch_range.begin(), batch_range.end());

    // Generate the data:
    auto cpu_input = compute_input<fftwAllocator<char>>(
        precision, cpu_itype, length, cpu_istride, cpu_idist, nbatch);
    auto cpu_input_copy = cpu_input; // copy of input (might get overwritten by FFTW).

    // Compute the Linfinity and L2 norm of the CPU output:
    VectorNorms cpu_input_norm;
    std::thread cpu_input_norm_thread([&]() {
        cpu_input_norm
            = norm(cpu_input, ilength, nbatch, precision, cpu_itype, cpu_istride, cpu_idist);
        if(verbose > 2)
        {
            std::cout << "CPU Input Linf norm:  " << cpu_input_norm.l_inf << "\n";
            std::cout << "CPU Input L2 norm:    " << cpu_input_norm.l_2 << "\n";
        }
    });
    if(verbose > 3)
    {
        std::cout << "CPU input:\n";
        printbuffer(precision, cpu_itype, cpu_input, ilength, cpu_istride, nbatch, cpu_idist);
    }

    // FFTW computation
    // NB: FFTW may overwrite input, even for out-of-place transforms.
    decltype(cpu_input) cpu_output;
    VectorNorms         cpu_output_norm;
    std::thread         cpu_output_thread([&]() {
        cpu_output = fftw_via_rocfft(length,
                                     cpu_istride,
                                     cpu_ostride,
                                     nbatch,
                                     cpu_idist,
                                     cpu_odist,
                                     precision,
                                     transformType,
                                     cpu_input);
        // Compute the Linfinity and L2 norm of the CPU output:
        cpu_output_norm
            = norm(cpu_output, olength, nbatch, precision, cpu_otype, cpu_ostride, cpu_odist);
        if(verbose > 2)
        {
            std::cout << "CPU Output Linf norm: " << cpu_output_norm.l_inf << "\n";
            std::cout << "CPU Output L2 norm:   " << cpu_output_norm.l_2 << "\n";
        }
        if(verbose > 3)
        {
            std::cout << "CPU output:\n";
            printbuffer(precision, cpu_otype, cpu_output, olength, cpu_ostride, nbatch, cpu_odist);
        }
    });
    // clean up threads if transform throws
    BOOST_SCOPE_EXIT_ALL(&cpu_output_thread, &cpu_input_norm_thread)
    {
        if(cpu_output_thread.joinable())
            cpu_output_thread.join();
        if(cpu_input_norm_thread.joinable())
            cpu_input_norm_thread.join();
    };

    // Set up GPU computations:
    for(const auto nbatch : batch_range)
    {
        for(const auto place : place_range)
        {
            for(const auto iotype : iotypes(transformType, place))
            {
                const rocfft_array_type itype = iotype.first;
                const rocfft_array_type otype = iotype.second;
                for(const auto istride : istride_range)
                {
                    for(const auto ostride : ostride_range)
                    {
                        if(verbose)
                        {
                            print_params(length,
                                         istride,
                                         ostride,
                                         nbatch,
                                         place,
                                         precision,
                                         transformType,
                                         itype,
                                         otype);
                        }

                        rocfft_transform(length,
                                         istride,
                                         ostride,
                                         nbatch,
                                         precision,
                                         transformType,
                                         itype,
                                         otype,
                                         place,
                                         cpu_istride,
                                         cpu_ostride,
                                         cpu_idist,
                                         cpu_odist,
                                         cpu_itype,
                                         cpu_otype,
                                         cpu_input_copy,
                                         cpu_output,
                                         cpu_output_norm,
                                         &cpu_output_thread);
                    }
                }
            }
        }
    }

    cpu_input_norm_thread.join();
    ASSERT_TRUE(std::isfinite(cpu_input_norm.l_inf));
    ASSERT_TRUE(std::isfinite(cpu_input_norm.l_2));

    if(cpu_output_thread.joinable())
        cpu_output_thread.join();
    ASSERT_TRUE(std::isfinite(cpu_output_norm.l_inf));
    ASSERT_TRUE(std::isfinite(cpu_output_norm.l_2));

    SUCCEED();
}


/*=============================================================================
    Copyright (c) 2009 Christopher Schmidt

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
==============================================================================*/

#ifndef BOOST_FUSION_VIEW_REVERSE_VIEW_DETAIL_AT_IMPL_HPP
#define BOOST_FUSION_VIEW_REVERSE_VIEW_DETAIL_AT_IMPL_HPP

#include <boost/fusion/support/config.hpp>
#include <boost/fusion/sequence/intrinsic/at.hpp>
#include <boost/mpl/minus.hpp>
#include <boost/mpl/int.hpp>

namespace boost { namespace fusion { namespace extension
{
    template <typename>
    struct at_impl;

    template <>
    struct at_impl<reverse_view_tag>
    {
        template <typename Seq, typename N>
        struct apply
        {
            typedef mpl::minus<typename Seq::size, mpl::int_<1>, N> real_n;

            typedef typename
                result_of::at<typename Seq::seq_type, real_n>::type
            type;

            BOOST_FUSION_GPU_ENABLED
            static type
            call(Seq& seq)
            {
                return fusion::at<real_n>(seq.seq);
            }
        };
    };
}}}

#endif


// Copyright (c) 2015-2021 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "duplicate_allele_fraction.hpp"

#include <algorithm>
#include <iterator>
#include <cassert>

#include <boost/variant.hpp>

#include "io/variant/vcf_record.hpp"
#include "allele_depth.hpp"
#include "duplicate_allele_depth.hpp"

namespace octopus { namespace csr {

const std::string DuplicateAlleleFraction::name_ = "DAF";

std::unique_ptr<Measure> DuplicateAlleleFraction::do_clone() const
{
    return std::make_unique<DuplicateAlleleFraction>(*this);
}

Measure::ValueType DuplicateAlleleFraction::get_value_type() const
{
    return double {};
}

Measure::ResultType DuplicateAlleleFraction::do_evaluate(const VcfRecord& call, const FacetMap& facets) const
{
    const auto allele_depths = boost::get<Array<Array<Optional<ValueType>>>>(AlleleDepth{}.evaluate(call, facets));
    const auto duplicate_allele_depths = boost::get<Array<Array<Optional<ValueType>>>>(DuplicateAlleleDepth{}.evaluate(call, facets));
    const auto num_alleles = call.alt().size() + 1;
    assert(allele_depths.size() == duplicate_allele_depths.size());
    Array<Array<Optional<ValueType>>> result(allele_depths.size(), Array<Optional<ValueType>>(num_alleles));
    for (std::size_t s {0}; s < allele_depths.size(); ++s) {
        assert(allele_depths[s].size() == num_alleles && duplicate_allele_depths[s].size() == num_alleles);
        for (std::size_t a {0}; a < num_alleles; ++a) {
            if (allele_depths[s][a] && duplicate_allele_depths[s][a]) {
                const auto allele_depth = boost::get<std::size_t>(*allele_depths[s][a]);
                if (allele_depth > 0) {
                    result[s][a] = static_cast<double>(boost::get<std::size_t>(*duplicate_allele_depths[s][a])) / allele_depth;
                } else {
                    result[s][a] = 0.0;
                }
            }
        }
    }
    return result;
}

Measure::ResultCardinality DuplicateAlleleFraction::do_cardinality() const noexcept
{
    return ResultCardinality::samples_and_alleles;
}

const std::string& DuplicateAlleleFraction::do_name() const
{
    return name_;
}

std::string DuplicateAlleleFraction::do_describe() const
{
    return "Fraction of realigned reads supporting ALT alleles identified as duplicates";
}

std::vector<std::string> DuplicateAlleleFraction::do_requirements() const
{

    return {"Samples", "OverlappingReads", "ReadAssignments"};
}

boost::optional<Measure::Aggregator> DuplicateAlleleFraction::do_aggregator() const noexcept
{
    return Measure::Aggregator::max_tail;
}

} // namespace csr
} // namespace octopus


#include "Avatar.h"
#include <string>
#include <boost/filesystem.hpp>
#include <cnpy.h>

#include "Version.h"
#include "Util.h"
#include "UtilCnpy.h"

#include "internal/AvatarHelpers.h"

namespace ark {
// SMPL model loading code
AvatarModel::AvatarModel(const std::string& model_dir,
                         bool limit_one_joint_per_point)
    : MODEL_DIR(model_dir) {
    using namespace boost::filesystem;
    path modelPath = model_dir.empty()
                         ? util::resolveRootPath("data/avatar-model")
                         : model_dir;
    path npzPath = modelPath / "model.npz";
    path posePriorPath = modelPath / "pose_prior.txt";
    if (exists(npzPath)) {
        // New (npz) format
        cnpy::npz_t npz = cnpy::npz_load(npzPath.string());
        size_t n_verts = npz["v_template"].shape[0];
        size_t n_joints = npz["kintree_table"].shape[1];
        size_t n_faces = npz["f"].shape[0];
        size_t n_shape_blends = npz["shapedirs"].shape[2];
        size_t n_blend_shapes = n_shape_blends;

        using util::assertShape;

        // Load kintree
        const auto& kttable_raw = npz.at("kintree_table");
        parent.resize(n_joints);
        parent.noalias() = util::loadUintMatrix(kttable_raw, 2, n_joints)
                               .template topRows<1>()
                               .cast<int>()
                               .transpose();
        _ARK_ASSERT_EQ(parent[0], -1);

        // Load base template
        const auto& verts_raw = npz.at("v_template");
        assertShape(verts_raw, {n_verts, 3});
        baseCloud.noalias() =
            util::loadFloatMatrix(verts_raw, 1, n_verts * 3).transpose();

        // Load triangle mesh
        const auto& faces_raw = npz.at("f");
        assertShape(faces_raw, {n_faces, 3});
        mesh =
            util::loadUintMatrix(faces_raw, n_faces, 3).transpose().cast<int>();

        // Load joint regressor
        const auto& jreg_raw = npz.at("J_regressor");
        assertShape(jreg_raw, {n_joints, n_verts});
        jointRegressor.resize(n_joints, n_verts);
        jointRegressor = util::loadFloatMatrix(jreg_raw, n_joints, n_verts)
                             .transpose()
                             .sparseView();
        jointRegressor.makeCompressed();

        // Load LBS weights
        const auto& wt_raw = npz.at("weights");
        assertShape(wt_raw, {n_verts, n_joints});
        weights.resize(n_joints, n_verts);
        weights = util::loadFloatMatrix(wt_raw, n_verts, n_joints)
                      .transpose()
                      .sparseView();
        weights.makeCompressed();

        // (Compatibility with existing code)
        assignedJoints.resize(n_verts);
        assignedPoints.resize(n_joints);
        for (int k = 0; k < weights.outerSize(); ++k) {
            for (Eigen::SparseMatrix<double>::InnerIterator it(weights, k); it;
                 ++it) {
                int r = it.row(), c = it.col();
                double wt = it.value();
                if (wt > 1e-12) {
                    assignedJoints[c].push_back({wt, r});
                    assignedPoints[r].push_back({wt, c});
                }
            }
        }
        for (int i = 0; i < n_joints; ++i) {
            std::sort(assignedPoints[i].begin(), assignedPoints[i].end(),
                      std::greater<std::pair<double, int>>());
        }
        for (int i = 0; i < n_verts; ++i) {
            std::sort(assignedJoints[i].begin(), assignedJoints[i].end(),
                      std::greater<std::pair<double, int>>());
        }

        // Blend shapes
        keyClouds.resize(3 * n_verts, n_blend_shapes);
        // Load shape-dep blend shapes
        const auto& sb_raw = npz.at("shapedirs");
        assertShape(sb_raw, {n_verts, 3, n_shape_blends});
        keyClouds.leftCols(n_shape_blends).noalias() =
            util::loadFloatMatrix(sb_raw, 3 * n_verts, n_shape_blends);

        // Load pose-dep blend shapes
        // (currently not used for efficiency reasons)
        // const auto& pb_raw = npz.at("posedirs");
        // assertShape(pb_raw, {n_verts, 3, n_pose_blends});
        // keyClouds.template rightCols<n_pose_blends>().noalias() =
        //     util::loadFloatMatrix(pb_raw, 3 * n_verts, n_pose_blends);

        // Compute the joint shape regressor
        useJointShapeRegressor = true;
        initialJointPos.noalias() =
            Eigen::Map<Eigen::Matrix<double, 3, Eigen::Dynamic>>(
                baseCloud.data(), 3, n_verts) *
            jointRegressor;
        jointShapeRegBase.noalias() = Eigen::template Map<Eigen::VectorXd>(
            initialJointPos.data(),
            initialJointPos.rows() * initialJointPos.cols(), 1);
        jointShapeReg.resize(3 * n_joints, n_shape_blends);
        for (int i = 0; i < n_shape_blends; ++i) {
            Eigen::Map<Eigen::Matrix<double, 3, Eigen::Dynamic>> keyVertsIn(
                keyClouds.col(i).data(), 3, n_verts);
            Eigen::Map<Eigen::Matrix<double, 3, Eigen::Dynamic>> jointsOut(
                jointShapeReg.col(i).data(), 3, n_joints);
            jointsOut.noalias() = keyVertsIn * jointRegressor;
        }
    } else {
        std::cerr << "WARNING: Using deprecated ad-hoc SMPL model format; "
                     "please download SMPL .npz model and place it at "
                     "data/avatar-model/model.npz\n";
        // Old ad-hoc format
        path skelPath = modelPath / "skeleton.txt";
        path jrPath = modelPath / "joint_regressor.txt";
        path jsrPath = modelPath / "joint_shape_regressor.txt";
        path meshPath = modelPath / "mesh.txt";

        baseCloud =
            loadPCDToPointVectorFast((modelPath / "model.pcd").string());

        int nJoints, nPoints;
        // Read skeleton file
        std::ifstream skel(skelPath.string());
        if (!skel) {
            std::cerr
                << "ERROR: Avatar model is invalid, skeleton file not found\n";
            std::exit(0);
        }
        skel >> nJoints >> nPoints;

        // Assume joints are given in topologically sorted order
        parent.resize(nJoints);
        initialJointPos.resize(3, nJoints);
        for (int i = 0; i < nJoints; ++i) {
            int id;
            std::string _name;  // throw away

            skel >> id;
            skel >> parent[id];
            skel >> _name >> initialJointPos(0, i) >> initialJointPos(1, i) >>
                initialJointPos(2, i);
        }
        parent[0] =
            -1;  // This should be in skeleton file, but just to make sure

        if (!skel) {
            std::cerr
                << "ERROR: Invalid avatar skeleton file: joint assignments "
                   "are not present\n";
            std::exit(0);
        }

        // Process joint assignments
        weights.resize(nJoints, nPoints);
        weights.reserve(3 * nPoints);
        assignedPoints.resize(nJoints);
        for (int i = 0; i < nJoints; ++i) {
            assignedPoints[i].reserve(7000 / nJoints);
        }
        assignedJoints.resize(nPoints);
        for (int i = 0; i < nPoints; ++i) {
            int nEntries;
            skel >> nEntries;
            assignedJoints[i].reserve(nEntries);
            for (int j = 0; j < nEntries; ++j) {
                int joint;
                double w;
                skel >> joint >> w;
                assignedJoints[i].emplace_back(w, joint);
                weights.insert(joint, i) = w;
            }
            std::sort(assignedJoints[i].begin(), assignedJoints[i].end(),
                      [](const std::pair<double, int>& a,
                         const std::pair<double, int>& b) {
                          return a.first > b.first;
                      });
            if (limit_one_joint_per_point) {
                assignedJoints[i].resize(1);
                assignedJoints[i].shrink_to_fit();
                assignedJoints[i][0].first = 1.0;
                assignedPoints[assignedJoints[i][0].second].emplace_back(1.0,
                                                                         i);
            } else {
                for (int j = 0; j < nEntries; ++j) {
                    assignedPoints[assignedJoints[i][j].second].emplace_back(
                        assignedJoints[i][j].first, i);
                }
            }
        }

        // Load all shape keys
        path keyPath = modelPath / "shapekey";
        if (is_directory(keyPath)) {
            int nShapeKeys = 0;
            for (directory_iterator it(keyPath); it != directory_iterator();
                 ++it)
                ++nShapeKeys;
            keyClouds.resize(3 * nPoints, nShapeKeys);

            int i = 0;
            for (directory_iterator it(keyPath); it != directory_iterator();
                 ++it) {
                keyClouds.col(i) =
                    loadPCDToPointVectorFast(it->path().string());
                ++i;
            }
        } else {
            std::cerr << "WARNING: no shape key directory found for avatar\n";
        }

        // Load joint regressor / joint shape regressor
        std::ifstream jsr(jsrPath.string());
        if (jsr) {
            int nShapeKeys;
            jsr >> nShapeKeys;
            jointShapeRegBase.resize(nJoints * 3);
            jointShapeReg.resize(nJoints * 3, nShapeKeys);
            for (int i = 0; i < jointShapeRegBase.rows(); ++i) {
                jsr >> jointShapeRegBase(i);
            }
            for (int i = 0; i < jointShapeReg.rows(); ++i) {
                for (int j = 0; j < jointShapeReg.cols(); ++j) {
                    jsr >> jointShapeReg(i, j);
                }
            }
            useJointShapeRegressor = true;
            jsr.close();
        } else {
            std::ifstream jr(jrPath.string());
            jointRegressor = Eigen::SparseMatrix<double>(nPoints, nJoints);
            if (jr) {
                jr >> nJoints;
                jointRegressor.reserve(nJoints * 10);
                for (int i = 0; i < nJoints; ++i) {
                    int nEntries;
                    jr >> nEntries;
                    int pointIdx;
                    double val;
                    for (int j = 0; j < nEntries; ++j) {
                        jr >> pointIdx >> val;
                        jointRegressor.insert(pointIdx, i) = val;
                    }
                }
                jr.close();
            } else {
                std::cerr << "WARNING: neither joint regressor nor joint shape "
                             "regressor found, model may be inaccurate with "
                             "nonzero shapekey weights\n";
            }
            useJointShapeRegressor = false;
        }

        // Maybe load mesh
        std::ifstream meshFile(meshPath.string());
        if (meshFile) {
            int nFaces;
            meshFile >> nFaces;
            mesh.resize(3, nFaces);
            for (int i = 0; i < nFaces; ++i) {
                meshFile >> mesh(0, i) >> mesh(1, i) >> mesh(2, i);
            }
        } else {
            std::cerr
                << "WARNING: mesh not found, maybe you are using an older "
                   "version of avatar data files? "
                   "Some functions will not work.\n";
        }
    }

    size_t totalAssignments = 0;
    for (size_t i = 0; i < assignedJoints.size(); ++i) {
        totalAssignments += assignedJoints[i].size();
    }

    // Maybe load pose prior
    posePrior.load(posePriorPath.string());
}
}  // namespace ark


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2014-2021, Oracle and/or its affiliates.

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_LINEAR_OR_AREAL_TO_AREAL_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_LINEAR_OR_AREAL_TO_AREAL_HPP

#include <boost/geometry/algorithms/detail/distance/linear_to_linear.hpp>
#include <boost/geometry/algorithms/detail/distance/strategy_utils.hpp>
#include <boost/geometry/algorithms/intersects.hpp>

#include <boost/geometry/core/point_type.hpp>

#include <boost/geometry/strategies/distance.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace distance
{

template <typename Linear, typename Areal, typename Strategies>
struct linear_to_areal
{
    typedef distance::return_t<Linear, Areal, Strategies> return_type;

    static inline return_type apply(Linear const& linear,
                                    Areal const& areal,
                                    Strategies const& strategies)
    {
        if ( geometry::intersects(linear, areal, strategies) )
        {
            return return_type(0);
        }

        return linear_to_linear
            <
                Linear, Areal, Strategies
            >::apply(linear, areal, strategies, false);
    }

    static inline return_type apply(Areal const& areal,
                                    Linear const& linear,
                                    Strategies const& strategies)
    {
        return apply(linear, areal, strategies);
    }
};

template <typename Areal1, typename Areal2, typename Strategies>
struct areal_to_areal
{
    typedef distance::return_t<Areal1, Areal2, Strategies> return_type;

    static inline return_type apply(Areal1 const& areal1,
                                    Areal2 const& areal2,
                                    Strategies const& strategies)
    {
        if ( geometry::intersects(areal1, areal2, strategies) )
        {
            return return_type(0);
        }

        return linear_to_linear
            <
                Areal1, Areal2, Strategies
            >::apply(areal1, areal2, strategies, false);
    }
};

}} // namespace detail::distance
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename Linear, typename Areal, typename Strategy>
struct distance
    <
        Linear, Areal, Strategy,
        linear_tag, areal_tag,
        strategy_tag_distance_point_segment, false
    >
    : detail::distance::linear_to_areal
        <
            Linear, Areal, Strategy
        >
{};

template <typename Areal, typename Linear, typename Strategy>
struct distance
    <
        Areal, Linear, Strategy,
        areal_tag, linear_tag,
        strategy_tag_distance_point_segment, false
    >
    : detail::distance::linear_to_areal
        <
            Linear, Areal, Strategy
        >
{};

template <typename Areal1, typename Areal2, typename Strategy>
struct distance
    <
        Areal1, Areal2, Strategy,
        areal_tag, areal_tag,
        strategy_tag_distance_point_segment, false
    >
    : detail::distance::areal_to_areal
        <
            Areal1, Areal2, Strategy
        >
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_LINEAR_OR_AREAL_TO_AREAL_HPP


#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC push_options
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx")
#include<bits/stdc++.h>
//#include <xmmintrin.h>
//#include <immintrin.h>
using namespace::std;
__attribute__((constructor))void init(){cin.tie(0);ios::sync_with_stdio(false);cout<<fixed<<setprecision(15);}
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/tree_policy.hpp>
//#include <ext/pb_ds/priority_queue.hpp>
//#include<ext/pb_ds/tag_and_trait.hpp>
// #include <boost/multiprecision/cpp_dec_float.hpp>
// #include <boost/multiprecision/cpp_int.hpp>
// namespace mp = boost::multiprecision;
// typedef mp::number<mp::cpp_dec_float<0>> cdouble;
// typedef mp::cpp_int cint;
//template<typename T>using pbds=__gnu_pbds::tree<T,__gnu_pbds::null_type,less<T>,__gnu_pbds::rb_tree_tag,__gnu_pbds::tree_order_statistics_node_update>;
//template<typename T>using pbds_map=__gnu_pbds::tree<T,T,less<T>,__gnu_pbds::rb_tree_tag,__gnu_pbds::tree_order_statistics_node_update>;
//template<typename T,typename E>using hash_map=__gnu_pbds::gp_hash_table<T,E>;
//template<typename T>using pqueue =__gnu_pbds::priority_queue<T, greater<T>,__gnu_pbds::rc_binomial_heap_tag>;
typedef int lint;
#define INF (1LL<<30)
#define IINF (1<<30)
#define EPS (1e-10)
#define endl ('\n')
#define MOD 1000000007LL
//#define MOD 998244353LL
typedef vector<lint> vec;
typedef vector<vector<lint>> mat;
typedef vector<vector<vector<lint>>> mat3;
typedef vector<string> svec;
typedef vector<vector<string>> smat;
template<typename T>inline void numout(T t){bool f=0;for(auto i:t){cout<<(f?" ":"")<<i<INF/2?i:"INF";f=1;}cout<<endl;}
template<typename T>inline void numout2(T t){for(auto i:t)numout(i);}
template<typename T>inline void output(T t){bool f=0;for(auto i:t){cout<<(f?" ":"")<<i;f=1;}cout<<endl;}
template<typename T>inline void output2(T t){for(auto i:t)output(i);}
template<typename T>inline void _output(T t){bool f=0;for(lint i=0;i<t.size();i++){cout<<f?"":" "<<t[i];f=1;}cout<<endl;}
template<typename T>inline void _output2(T t){for(lint i=0;i<t.size();i++)output(t[i]);}
#define rep(i,n) for(lint i=0;i<lint(n);++i)
#define repi(i,a,b) for(lint i=lint(a);i<(lint)(b);++i)
#define rrep(i,n) for(lint i=lint(n)-1;i>=0;--i)
#define rrepi(i,a,b) for(lint i=lint(b)-1;i>=lint(a);--i)
#define irep(i) for(lint i=0;;++i)
#define all(n) begin(n),end(n)
#define dist(a,b,c,d) sqrt(pow(a-c,2)+pow(b-d,2))
inline lint gcd(lint A,lint B){return B?gcd(B,A%B):A;}
inline lint lcm(lint A,lint B){return A/gcd(A,B)*B;}
// inline cint cgcd(cint A,cint B){return B?cgcd(B,A%B):A;}
// inline cint clcm(cint A,cint B){return A/cgcd(A,B)*B;}
bool chmin(auto& s,const auto& t){bool res=s>t;s=min(s,t);return res;}
bool chmax(auto& s,const auto& t){bool res=s<t;s=max(s,t);return res;}
const vector<lint> dx={1,0,-1,0,1,1,-1,-1};
const vector<lint> dy={0,1,0,-1,-1,1,-1};
#define SUM(v) accumulate(all(v),0LL)
auto call=[](auto f,auto... args){return f(f,args...);};

unsigned long xor128(){
    static unsigned long x=123456789,y=362436069,z=521288629,w=88675123;
    unsigned long t;
    t=(x^(x<<11));x=y;y=z;z=w; return( w=(w^(w>>19))^(t^(t>>8)) );
}

double timer(auto f){
    auto s=chrono::system_clock::now();
    f();
    auto e=chrono::system_clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>(e-s).count();
}

int main(){
    lint h,w;
    cin>>w>>h;
    cin.ignore();
    vector<string>s(h);
    rep(i,h){
        getline(cin,s[i]);
    }
    lint idx=0;
    vector<vector<lint>> point(h,vector<lint>(w));
    // game loop
    vector<lint>pre(5,0);
    auto f=[&](){
        int myScore;
        int opponentScore;
        cin >> myScore >> opponentScore; cin.ignore();
        int visiblePacCount; // all your pacs and enemy pacs in sight
        cin >> visiblePacCount; cin.ignore();
        vector<pair<lint,lint>> enemy;
        vector<tuple<lint,lint,lint>> player;
        for (int i = 0; i < visiblePacCount; i++) {
            int pacId; // pac number (unique within a team)
            bool mine; // true if this pac is yours
            int _x; // position in the grid
            int _y; // position in the grid
            string typeId; // unused in wood leagues
            int speedTurnsLeft; // unused in wood leagues
            int abilityCooldown; // unused in wood leagues
            cin >> pacId >> mine >> _y >> _x >> typeId >> speedTurnsLeft >> abilityCooldown; cin.ignore();
            if(mine){
                player.emplace_back(_x,_y,pacId);
            }else{
                enemy.emplace_back(_x,_y);
            }
        }
        vector<vector<lint>>v(player.size(),vec(4,0));
        for(auto e:enemy){
            rep(i,player.size()){
                lint mx=get<0>(player[i]),my=get<1>(player[i]);
                rep(j,4){
                    lint x=(mx+dx[j]+h)%h,y=(my+dy[j]+w)%w;
                    while(x>=h)x-=h;
                    while(y>=w)y-=w;
                    if((abs(x-e.first)+abs(y-e.second))<=1){
                        v[i][j]--;
                    }
                }
            }
        }
        rep(k,player.size()){
            rep(i,player.size()){
                lint mx=get<0>(player[i]),my=get<1>(player[i]);
                lint ex=get<0>(player[k]),ey=get<1>(player[k]);
                rep(j,4){
                    lint x=(mx+dx[j]+h)%h,y=(my+dy[j]+w)%w;
                    if((abs(x-ex)+abs(y-ey))<=1){
                        v[i][j]--;
                    }
                }
            }
        }
        int pellet; // all pellets in sight
        cin >> pellet; cin.ignore();
        rep(k,pellet){
            int x;
            int y;
            int value; // amount of points this pellet is worth
            cin >> y >> x >> value; cin.ignore();
            rep(i,player.size()){
                lint mx=get<0>(player[i]),my=get<1>(player[i]);
                rep(j,4){
                    lint tx=(mx+dx[j]+h)%h,ty=(my+dy[j]+w)%w;
                    if((abs(tx-x)+abs(ty-y))==0)v[i][j]+=value;
                }
            }
        }
        rep(k,player.size()){
            v[k][(pre[k]+2)&3]--;
            lint mx=get<0>(player[k]),my=get<1>(player[k]);
            tuple<lint,lint,lint> mn=make_tuple(-INF,mx,my);
            rep(i,4){
                lint tx=(mx+dx[i]+h)%h,ty=(my+dy[i]+w)%w;
                if(s[tx][ty]=='#')continue;
                if(get<0>(mn)<=v[k][i]){
                    mn=make_tuple(v[k][i],tx,ty);
                    pre[get<2>(player[k])]=i;
                }
            }
            if(get<0>(mn)<0){
                vector<lint>tmp;
                rep(j,4){
                    lint tx=(mx+dx[j]+h)%h,ty=(my+dy[j]+w)%w;
                    if(s[tx][ty]=='#')continue;
                    if(v[k][j]==0){
                        tmp.push_back(j);
                    }
                }
                if(tmp.size()==0){
                    tmp.push_back(pre[k]);
                }
                lint i=tmp[myScore%tmp.size()];
                mn=make_tuple(v[k][i],(mx+dx[i]+h)%h,(my+dy[i]+w)%w);
                pre[get<2>(player[k])]=i;
                idx=(idx+i)%4;
            }
            //point[get<2>(mn)][get<1>(mn)]+=v[mx][my]*0.3;
            //point[get<2>(mn)][get<1>(mn)]--;
            if(k)cout<<"|";
            cout<<"MOVE "<<get<2>(player[k])<<" "<<get<2>(mn)<<" "<<get<1>(mn);
        }
        cout<<endl;
         // MOVE <pacId> <x> <y>
    };
    while(1){
        cerr<<timer(f)<<endl;
    }
}


#include "alexandria.hpp"

#include <iostream>

#include <boost/algorithm/string.hpp>

#include <sophiatx/utilities/key_conversion.hpp>
#include <sophiatx/protocol/transaction.hpp>

#include <fc/io/json.hpp>
#include <fc/real128.hpp>
#include <fc/crypto/base58.hpp>
#include <fc/crypto/aes.hpp>
#include <fc/crypto/rand.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/api.hpp>

using namespace sophiatx::utilities;
using namespace sophiatx::protocol;
using namespace fc::ecc;
using namespace std;

namespace {

struct memo_data {

   static std::optional<memo_data> from_string( string str ) {
      try {
         if( str.size() > sizeof(memo_data)) {
            auto data = fc::from_base58( str );
            auto m  = fc::raw::unpack_from_vector<memo_data>( data, 0 );
            FC_ASSERT( string(m) == str );
            return m;
         }
      } catch ( ... ) {}
      return std::optional<memo_data>();
   }

   int64_t         nonce = 0;
   uint64_t        check = 0;
   vector<char>    encrypted;

   operator string()const {
      auto data = fc::raw::pack_to_vector(*this);
      auto base58 = fc::to_base58( data );
      return base58;
   }
};

bool generate_private_key(char *private_key, char *public_key) {
   try {
      private_key_type priv_key = fc::ecc::private_key::generate();
      public_key_type pub_key = priv_key.get_public_key();
      strcpy(private_key, key_to_wif(priv_key).c_str());
      auto public_key_str = fc::json::to_string(pub_key);
      strcpy(public_key, public_key_str.substr(1, public_key_str.size() - 2).c_str());
      return true;
   } catch (const fc::exception& e) {
      return false;
   }
}

bool get_public_key(const char *private_key, char *public_key) {
   if(private_key) {
      try {
         auto priv_key = sophiatx::utilities::wif_to_key(string(private_key));
         if(priv_key) {
            public_key_type pub_key = priv_key->get_public_key();
            auto public_key_str = fc::json::to_string(pub_key);
            strcpy(public_key, public_key_str.substr(1, public_key_str.size() - 2).c_str());
            return true;
         }
      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool generate_key_pair_from_brain_key(const char *brain_key, char *private_key, char *public_key) {
   if(brain_key) {
      try {
         fc::sha512 h = fc::sha512::hash(string(brain_key) + " 0");
         auto priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(h));
         public_key_type pub_key = priv_key.get_public_key();
         strcpy(private_key, key_to_wif(priv_key).c_str());
         auto public_key_str = fc::json::to_string(pub_key);
         strcpy(public_key, public_key_str.substr(1, public_key_str.size() - 2).c_str());
         return true;
      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool get_transaction_digest(const char *transaction, const char *chain_id, char *digest) {
   if(transaction && chain_id) {
      try {
         string tx_str(transaction);
         fc::variant v = fc::json::from_string( tx_str, fc::json::strict_parser );
         signed_transaction stx;
         fc::from_variant( v, stx);
         digest_type dig = stx.sig_digest(fc::sha256(string(chain_id)));
         strcpy(digest, dig.str().c_str());
         return true;
      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool sign_digest(const char *digest, const char *private_key, char *signed_digest) {
   if(digest && private_key) {
      try {
         fc::sha256 dig(string(digest, strlen(digest)));
         string private_k_str(private_key);
         auto priv_key = sophiatx::utilities::wif_to_key(private_k_str);
         if(priv_key) {
            auto sig = priv_key->sign_compact(dig, fc::ecc::bip_0062);
            string result = fc::json::to_string(sig);
            strcpy(signed_digest, result.substr(1, result.size() - 2).c_str());
            return true;
         }
      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool add_signature(const char *transaction, const char *signature, char *signed_tx) {
   if(transaction && signature) {
      try {
         string tx_str(transaction);
         fc::variant v = fc::json::from_string( tx_str, fc::json::strict_parser );
         signed_transaction stx;
         fc::from_variant( v, stx );

         compact_signature sig;
         fc::from_hex( string(signature), (char*)sig.begin(), sizeof(compact_signature) );

         stx.signatures.push_back(sig);
         strcpy(signed_tx, fc::json::to_string(stx).c_str());
         return true;

      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool verify_signature(const char *digest, const char *public_key, const char *signed_digest) {
   if(digest && public_key && signed_digest) {
      try {
         fc::sha256 dig(string(digest, strlen(digest)));

         fc::variant v = fc::json::from_string( string(public_key), fc::json::relaxed_parser );
         public_key_type pub_key;
         fc::from_variant( v, pub_key );

         compact_signature sig;
         fc::from_hex( string(signed_digest), (char*)sig.begin(), sizeof(compact_signature) );

         if(pub_key == fc::ecc::public_key::recover_key(sig, dig, fc::ecc::bip_0062)) {
            return true;
         }

      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool encrypt_memo(const char *memo, const char *private_key, const char *public_key, char *encrypted_memo) {
   if(memo && private_key && public_key) {
      try {
         memo_data m;

         auto priv_key = sophiatx::utilities::wif_to_key(string(private_key));

         if(priv_key) {
            fc::variant v = fc::json::from_string( string(public_key), fc::json::relaxed_parser );
            public_key_type pub_key;
            fc::from_variant( v, pub_key );

            m.nonce = fc::time_point::now().time_since_epoch().count();

            auto shared_secret = priv_key->get_shared_secret( pub_key );

            fc::sha512::encoder enc;
            fc::raw::pack( enc, m.nonce );
            fc::raw::pack( enc, shared_secret );
            auto encrypt_key = enc.result();

            m.encrypted = fc::aes_encrypt( encrypt_key, fc::raw::pack_to_vector(string(memo)) );
            m.check = fc::sha256::hash( encrypt_key )._hash[0];
            strcpy(encrypted_memo, string(m).c_str());
            return true;
         }

      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool decrypt_memo(const char *memo, const char *private_key, const char* public_key, char *decrypted_memo) {
   if(memo && private_key) {
      try {
         string str_memo(memo);
         auto m = memo_data::from_string( str_memo );

         if( m ) {
            fc::sha512 shared_secret;
            auto priv_key = sophiatx::utilities::wif_to_key(string(private_key));
            if(priv_key) {
               fc::variant v = fc::json::from_string( string(public_key), fc::json::relaxed_parser );
               public_key_type pub_key;
               fc::from_variant( v, pub_key );

               shared_secret = priv_key->get_shared_secret(pub_key);

               fc::sha512::encoder enc;
               fc::raw::pack( enc, m->nonce );
               fc::raw::pack( enc, shared_secret );
               auto encryption_key = enc.result();

               uint64_t check = fc::sha256::hash( encryption_key )._hash[0];
               if( check != m->check ) return false;

               vector<char> decrypted = fc::aes_decrypt( encryption_key, m->encrypted );
               strcpy(decrypted_memo, fc::raw::unpack_from_vector<std::string>( decrypted, 0 ).c_str());

               return true;
            }
         }
      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool get_shared_secret(const char *private_key, const char* public_key, char *shared_secret) {
   if(public_key && private_key) {
      try {
            fc::sha512 shared_sec;
            auto priv_key = sophiatx::utilities::wif_to_key(string(private_key));
            if(priv_key) {
               fc::variant v = fc::json::from_string( string(public_key), fc::json::relaxed_parser );
               public_key_type pub_key;
               fc::from_variant( v, pub_key );

               shared_sec = priv_key->get_shared_secret(pub_key);
               strcpy(shared_secret, shared_sec.str().c_str());

               return true;
         }
      } catch (const fc::exception& e) {
         return false;
      }
   }
   return false;
}

bool base64_decode(const char *input, char *output) {
   try {
      auto out = fc::base64_decode(string(input));
      strcpy(output, out.c_str());
   } catch (const fc::exception& e) {
      return false;
   }
   return true;
}

bool base64_encode(const char *input, char *output) {
   try {
      auto out = fc::base64_encode(string(input));
      strcpy(output, out.c_str());
   } catch (const fc::exception& e) {
      return false;
   }
   return true;
}

} //

FC_REFLECT( memo_data, (nonce)(check)(encrypted) )


#pragma once

#include "MPU9255.hpp"
#include <boost/endian/arithmetic.hpp>
#include <Eigen/Dense>

namespace icarus
{
    namespace mpu9255 {
        enum ActiveLevel : uint8_t
        {
            high,
            low,
        };

        enum PinMode : uint8_t
        {
            pushPull,
            openDrain,
        };

        enum class AccelerometerRange : uint8_t
        {
            g2,
            g4,
            g8,
            g16,
        };

        enum class GyroscopeRange : uint8_t
        {
            dps250,
            dps500,
            dps1000,
            dps2000,
        };

        enum class ClockSource : uint8_t
        {
            internal20MHz = 0,
            bestAvailable = 1,
            stopped = 7
        };

        struct SampleRateDivider
        {
            enum { address = 25 };
            uint8_t divider;
        };

        struct Configuration
        {
            enum { address = 26 };
            uint8_t digitalLowPassFilter : 3;
            uint8_t fSync : 3;
            uint8_t fifoMode : 1;
        };

        struct AccelerometerConfiguration1
        {
            enum { address = 28 };
            uint8_t : 3;
            AccelerometerRange fullScaleSelect : 2;
            bool zAxisSelfTest : 1;
            bool yAxisSelfTest : 1;
            bool xAxisSelfTest : 1;
        };

        struct AccelerometerConfiguration2
        {
            enum { address = 29 };
            uint8_t digitalLowPassFilterConfiguration : 3;
            bool disableLowPassFilter : 1;
        };

        struct GyroscopeConfiguration
        {
            enum { address = 27 };
            uint8_t fChoiceB : 2;
            uint8_t : 1;
            GyroscopeRange fullScaleRange : 2;
            bool zAxisSelfTest : 1;
            bool yAxisSelfTest : 1;
            bool xAxisSelfTest : 1;
        };

        struct TemperatureRegister
        {
            enum { address = 65 };
            boost::endian::big_int16_t temperature;
        };

        struct AccelerometerMeasurements
        {
            enum { address = 59 };
            Eigen::Matrix<boost::endian::big_int16_t, 3, 1> acceleration;
        };

        struct GyroscopeMeasurements
        {
            enum { address = 67 };
            Eigen::Matrix<boost::endian::big_int16_t, 3, 1> angularVelocity;
        };

        struct InterruptBypassConfiguration
        {
            enum { address = 55 };

            uint8_t : 1;
            bool enableBypass : 1;
            bool enableFsyncInterrupt : 1;
            ActiveLevel fsyncActiveLevel : 1;
            bool clearInterruptOnRead : 1;
            bool latchInterrputPin : 1;
            PinMode interruptPinMode : 1;
            ActiveLevel interruptPinLevel : 1;
        };

        struct PowerManagement1
        {
            enum { address = 107 };
            ClockSource clockSelect : 3;
            bool powerDownProportionalToAbsoulteTemperatureVoltageGenerator : 1;
            bool gyroscopeStandby : 1;
            bool cycle : 1;
            bool sleep : 1;
            bool hardReset : 1;
        };

        struct WhoAmI
        {
            enum { address = 117 };
            uint8_t id;
        };
    }

    template<typename RegisterBank>
    MPU9255<RegisterBank>::MPU9255(RegisterBank * device) :
        mDevice(device),
        mGyroscopeScaleRange(0)
    {}

    template<typename RegisterBank>
    void MPU9255<RegisterBank>::initialize()
    {
        using namespace mpu9255;

        mDevice->template read<WhoAmI>([](auto & reg){
            if (reg.id != 0x73) {
                std::stringstream ss;
                    ss << "Unrecognized device on the bus. Expected MPU9255: id: "
                    << std::hex << 0x73 << ", got: " << int(reg.id);
                throw std::runtime_error(ss.str());
            }
        });

        mDevice->template write<AccelerometerConfiguration1>([](auto & config) {
            config.fullScaleSelect = AccelerometerRange::g8;
        });
        constexpr float STANDARD_GRAVITY =  9.80665;
        mAccelerationScale = STANDARD_GRAVITY / 4096.0;

        mDevice->template write<AccelerometerConfiguration2>([](auto & config) {
            config.disableLowPassFilter = false;
            config.digitalLowPassFilterConfiguration = 2;
        });

        mDevice->template write<Configuration>([](auto & config) {
            config.digitalLowPassFilter = 2;
            config.fSync = 0;
            config.fifoMode = 0;
        });

        mDevice->template write<GyroscopeConfiguration>([](auto & config) {
            config.fChoiceB = 0;
            config.fullScaleRange = mpu9255::GyroscopeRange::dps250;
        });

        mDevice->template write<PowerManagement1>([](auto & config) {
            config.clockSelect = mpu9255::ClockSource::bestAvailable;
            config.powerDownProportionalToAbsoulteTemperatureVoltageGenerator = false;
            config.gyroscopeStandby = false;
            config.cycle = false;
            config.sleep = false;
            config.hardReset = false;
        });
    }

    template<typename RegisterBank>
    void MPU9255<RegisterBank>::i2cBypass(bool enable)
    {
        using namespace mpu9255;

        mDevice->template write<InterruptBypassConfiguration>([enable](auto & config) {
            config.enableBypass = enable;
        });
    }

    template<typename RegisterBank>
    void MPU9255<RegisterBank>::read()
    {
        using namespace mpu9255;

        mDevice->template read<TemperatureRegister>([this](auto const& reg){
            constexpr float KELVINS_PER_LSB = 1.0 / 333.87;
            constexpr float OFFSET_FROM_ABSOLUTE_ZERO = 294.15;
            mTemperature = static_cast<float>(reg.temperature) * KELVINS_PER_LSB + OFFSET_FROM_ABSOLUTE_ZERO;
        });

        mDevice->template read<AccelerometerMeasurements>([this](auto const& reg){
            mAcceleration = reg.acceleration.template cast<float>() * mAccelerationScale;
        });

        int newRange;
        mDevice->template read<GyroscopeMeasurements>([this, &newRange](auto const& reg){
            constexpr float radiansPerDegree = (2 * M_PI) / 360.0;
            constexpr float baseSensitivity = 1.0 / 131.0 * radiansPerDegree;

            float const sensitivity = (1 << mGyroscopeScaleRange) * baseSensitivity;
            mAngularVelocity = reg.angularVelocity.template cast<float>() * sensitivity;

            constexpr int highBound = 23170; // reading above which we attempt to increase full scale select
            constexpr int lowBound = 8192; // reading below which we attempt to decrease full scale select
            int const maximum = reg.angularVelocity.template cast<int>().cwiseAbs().maxCoeff();
            if (maximum > highBound && mGyroscopeScaleRange < 3) {
                newRange = mGyroscopeScaleRange + 1;
            } else if (maximum < lowBound && mGyroscopeScaleRange > 0) {
                newRange = mGyroscopeScaleRange - 1;
            } else {
                newRange = mGyroscopeScaleRange;
            }
        });

        if (newRange != mGyroscopeScaleRange) {
            mDevice->template write<mpu9255::GyroscopeConfiguration>([=](auto & config) {
                config.fChoiceB = 0;
                config.fullScaleRange = (mpu9255::GyroscopeRange) newRange;
            });

            mGyroscopeScaleRange = newRange;
        }
    }
}


/****************************************************************************
*
*    Copyright (c) 2019 Vivante Corporation
*
*    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.
*
*****************************************************************************/

#pragma once

#include <backendsCommon/CpuTensorHandle.hpp>
#include <backendsCommon/Workload.hpp>
#include <backendsCommon/WorkloadData.hpp>
#include <boost/log/trivial.hpp>
#include "TNpuWorkloads.hpp"

namespace armnn {
template <typename armnn::DataType... DataTypes>
class NpuL2NormalizationWorkload
    : public TNpuWorkload<L2NormalizationQueueDescriptor, DataTypes...> {
   public:
    using base_type = TNpuWorkload<L2NormalizationQueueDescriptor, DataTypes...>;
    explicit NpuL2NormalizationWorkload(const L2NormalizationQueueDescriptor& descriptor,
                                        const WorkloadInfo& info)
        : TNpuWorkload<L2NormalizationQueueDescriptor, DataTypes...>(descriptor, info),
          m_Eps(descriptor.m_Parameters.m_Eps),
          m_DataLayout(descriptor.m_Parameters.m_DataLayout) {
        // Add input operand
        // Only 1 input
        std::vector<uint32_t> inOperandIds;
        NpuTensorHandler* inputTensorHandle =
            dynamic_cast<NpuTensorHandler*>(descriptor.m_Inputs[0]);
        uint32_t inputOperandId = this->AddOperandAndSetValue(
            inputTensorHandle->GetTensorInfo(), inputTensorHandle->GetShape(), nullptr);
        inOperandIds.push_back(inputOperandId);

        // Add layout operand
        int32_t layoutCode = m_DataLayout == armnn::DataLayout::NCHW
                                 ? int32_t(nnrt::DataLayout::NCHW)
                                 : int32_t(nnrt::DataLayout::NHWC);
        inOperandIds.push_back(this->AddOperandAndSetValue(layoutCode));

        // Add output operand
        std::vector<uint32_t> outOperandIds;
        NpuTensorHandler* outputTensorHandle =
            dynamic_cast<NpuTensorHandler*>(descriptor.m_Outputs[0]);
        uint32_t outputTensorId = this->AddOperandAndSetValue(
            outputTensorHandle->GetTensorInfo(), outputTensorHandle->GetShape(), nullptr);
        outOperandIds.push_back(outputTensorId);

        this->AddOperation(nnrt::OperationType::L2_NORMALIZATION,
                           inOperandIds.size(),
                           inOperandIds.data(),
                           outOperandIds.size(),
                           outOperandIds.data());
    }

   private:
    // Used to avoid dividing by zero.
    float m_Eps;
    // The data layout to be used (NCHW, NHWC).
    DataLayout m_DataLayout;
};
using NpuL2NormalizationFloat32Workload = NpuL2NormalizationWorkload<armnn::DataType::Float32>;
using NpuL2NormalizationFloat16Workload = NpuL2NormalizationWorkload<armnn::DataType::Float16>;
using NpuL2NormalizationUint8Workload =
    NpuL2NormalizationWorkload<armnn::DataType::QuantisedAsymm8>;
}  // namespace armnn


/**
 * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS)
 *
 * This file is part of libbitcoin.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <bitcoin/bitcoin/wallet/hd_private.hpp>

#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <boost/program_options.hpp>
#include <bitcoin/bitcoin/constants.hpp>
#include <bitcoin/bitcoin/define.hpp>
#include <bitcoin/bitcoin/formats/base_58.hpp>
#include <bitcoin/bitcoin/math/checksum.hpp>
#include <bitcoin/bitcoin/math/elliptic_curve.hpp>
#include <bitcoin/bitcoin/math/hash.hpp>
#include <bitcoin/bitcoin/math/limits.hpp>
#include <bitcoin/bitcoin/utility/assert.hpp>
#include <bitcoin/bitcoin/utility/container_source.hpp>
#include <bitcoin/bitcoin/utility/data.hpp>
#include <bitcoin/bitcoin/utility/endian.hpp>
#include <bitcoin/bitcoin/utility/istream_reader.hpp>
#include <bitcoin/bitcoin/utility/serializer.hpp>
#include <bitcoin/bitcoin/wallet/ec_private.hpp>
#include <bitcoin/bitcoin/wallet/ec_public.hpp>

namespace libbitcoin {
namespace wallet {

const uint64_t hd_private::mainnet = to_prefixes(76066276,
    hd_public::mainnet);

const uint64_t hd_private::testnet = to_prefixes(70615956,
    hd_public::testnet);

hd_private::hd_private()
  : hd_public(), secret_(null_hash)
{
}

hd_private::hd_private(const hd_private& other)
  : hd_public(other), secret_(other.secret_)
{
}

hd_private::hd_private(const data_chunk& seed, uint64_t prefixes)
  : hd_private(from_seed(seed, prefixes))
{
}

// This reads the private version and sets the public to mainnet.
hd_private::hd_private(const hd_key& private_key)
  : hd_private(from_key(private_key, hd_public::mainnet))
{
}

// This reads the private version and sets the public to mainnet.
hd_private::hd_private(const std::string& encoded)
    : hd_private(from_string(encoded, hd_public::mainnet))
{
}

// This reads the private version and sets the public.
hd_private::hd_private(const hd_key& private_key, uint32_t prefix)
  : hd_private(from_key(private_key, prefix))
{
}

// This validates the private version and sets the public.
hd_private::hd_private(const hd_key& private_key, uint64_t prefixes)
  : hd_private(from_key(private_key, prefixes))
{
}

// This reads the private version and sets the public.
hd_private::hd_private(const std::string& encoded, uint32_t prefix)
  : hd_private(from_string(encoded, prefix))
{
}

// This validates the private version and sets the public.
hd_private::hd_private(const std::string& encoded, uint64_t prefixes)
  : hd_private(from_string(encoded, prefixes))
{
}

hd_private::hd_private(const ec_secret& secret,
    const hd_chain_code& chain_code, const hd_lineage& lineage)
  : hd_public(from_secret(secret, chain_code, lineage)),
    secret_(secret)
{
}

// Factories.
// ----------------------------------------------------------------------------

hd_private hd_private::from_seed(data_slice seed, uint64_t prefixes)
{
    // This is a magic constant from BIP32.
    static const data_chunk magic(to_chunk("Bitcoin seed"));

    const auto intermediate = split(hmac_sha512_hash(seed, magic));

    // The key is invalid if parse256(IL) >= n or 0:
    if (!verify(intermediate.left))
        return{};

    const auto master = hd_lineage
    {
        prefixes,
        0x00,
        0x00000000,
        0x00000000
    };

    return hd_private(intermediate.left, intermediate.right, master);
}

hd_private hd_private::from_key(const hd_key& key, uint32_t public_prefix)
{
    const auto prefix = from_big_endian_unsafe<uint32_t>(key.begin());
    return from_key(key, to_prefixes(prefix, public_prefix));
}

hd_private hd_private::from_key(const hd_key& key, uint64_t prefixes)
{
    stream_source<hd_key> istream(key);
    istream_reader reader(istream);

    const auto prefix = reader.read_4_bytes_big_endian();
    const auto depth = reader.read_byte();
    const auto parent = reader.read_4_bytes_big_endian();
    const auto child = reader.read_4_bytes_big_endian();
    const auto chain = reader.read_forward<hd_chain_code_size>();
    reader.read_byte();
    const auto secret = reader.read_forward<ec_secret_size>();

    // Validate the prefix against the provided value.
    if (prefix != to_prefix(prefixes))
        return{};

    const hd_lineage lineage
    {
        prefixes,
        depth,
        parent,
        child
    };

    return hd_private(secret, chain, lineage);
}

hd_private hd_private::from_string(const std::string& encoded,
    uint32_t public_prefix)
{
    hd_key key;
    if (!decode_base58(key, encoded))
        return{};

    return hd_private(from_key(key, public_prefix));
}

hd_private hd_private::from_string(const std::string& encoded,
    uint64_t prefixes)
{
    hd_key key;
    return decode_base58(key, encoded) ? hd_private(key, prefixes) :
        hd_private{};
}

// Cast operators.
// ----------------------------------------------------------------------------

hd_private::operator const ec_secret&() const
{
    return secret_;
}

// Serializer.
// ----------------------------------------------------------------------------

std::string hd_private::encoded() const
{
    return encode_base58(to_hd_key());
}

/// Accessors.
// ----------------------------------------------------------------------------

const ec_secret& hd_private::secret() const
{
    return secret_;
}

// Methods.
// ----------------------------------------------------------------------------

// HD keys do not carry a payment address prefix (just like WIF).
// So we are currently not converting to ec_public or ec_private.

hd_key hd_private::to_hd_key() const
{
    static constexpr uint8_t private_key_padding = 0x00;

    hd_key out;
    build_checked_array(out,
    {
        to_big_endian(to_prefix(lineage_.prefixes)),
        to_array(lineage_.depth),
        to_big_endian(lineage_.parent_fingerprint),
        to_big_endian(lineage_.child_number),
        chain_,
        to_array(private_key_padding),
        secret_
    });

    return out;
}

hd_public hd_private::to_public() const
{
    return hd_public(((hd_public)*this).to_hd_key(),
        hd_public::to_prefix(lineage_.prefixes));
}

hd_private hd_private::derive_private(uint32_t index) const
{
    constexpr uint8_t depth = 0;

    const auto data = (index >= hd_first_hardened_key) ?
        splice(to_array(depth), secret_, to_big_endian(index)) :
        splice(point_, to_big_endian(index));

    const auto intermediate = split(hmac_sha512_hash(data, chain_));

    // The child key ki is (parse256(IL) + kpar) mod n:
    auto child = secret_;
    if (!ec_add(child, intermediate.left))
        return{};

    if (lineage_.depth == max_uint8)
        return{};

    const hd_lineage lineage
    {
        lineage_.prefixes,
        static_cast<uint8_t>(lineage_.depth + 1),
        fingerprint(),
        index
    };

    return hd_private(child, intermediate.right, lineage);
}

hd_public hd_private::derive_public(uint32_t index) const
{
    return derive_private(index).to_public();
}

// Operators.
// ----------------------------------------------------------------------------

hd_private& hd_private::operator=(hd_private other)
{
    swap(*this, other);
    return *this;
}

bool hd_private::operator<(const hd_private& other) const
{
    return encoded() < other.encoded();
}

bool hd_private::operator==(const hd_private& other) const
{
    return secret_ == other.secret_ && valid_ == other.valid_ &&
        chain_ == other.chain_ && lineage_ == other.lineage_ &&
        point_ == other.point_;
}

bool hd_private::operator!=(const hd_private& other) const
{
    return !(*this == other);
}

// We must assume mainnet for public version here.
// When converting this to public a clone of this key should be used, with the
// public version specified - after validating the private version.
std::istream& operator>>(std::istream& in, hd_private& to)
{
    std::string value;
    in >> value;
    to = hd_private(value, hd_public::mainnet);

    if (!to)
    {
        using namespace boost::program_options;
        BOOST_THROW_EXCEPTION(invalid_option_value(value));
    }

    return in;
}

std::ostream& operator<<(std::ostream& out, const hd_private& of)
{
    out << of.encoded();
    return out;
}

// friend function, see: stackoverflow.com/a/5695855/1172329
void swap(hd_private& left, hd_private& right)
{
    using std::swap;

    // Must be unqualified (no std namespace).
    swap(static_cast<hd_public&>(left), static_cast<hd_public&>(right));
    swap(left.secret_, right.secret_);
}

} // namespace wallet
} // namespace libbitcoin


#ifndef STAN_SERVICES_EXPERIMENTAL_ADVI_LOWRANK_HPP
#define STAN_SERVICES_EXPERIMENTAL_ADVI_LOWRANK_HPP

#include <stan/callbacks/interrupt.hpp>
#include <stan/callbacks/logger.hpp>
#include <stan/callbacks/writer.hpp>
#include <stan/services/util/experimental_message.hpp>
#include <stan/services/util/initialize.hpp>
#include <stan/services/util/create_rng.hpp>
#include <stan/io/var_context.hpp>
#include <stan/variational/advi.hpp>
#include <boost/random/additive_combine.hpp>
#include <string>
#include <vector>

namespace stan {
namespace services {
namespace experimental {
namespace advi {

/**
 * Runs low rank ADVI.
 *
 * @tparam Model A model implementation
 * @param[in] model Input model to test (with data already instantiated)
 * @param[in] init var context for initialization
 * @param[in] random_seed random seed for the random number generator
 * @param[in] chain chain id to advance the random number generator
 * @param[in] init_radius radius to initialize
 * @param[in] grad_samples number of samples for Monte Carlo estimate
 *   of gradients
 * @param[in] elbo_samples number of samples for Monte Carlo estimate
 *   of ELBO
 * @param[in] max_iterations maximum number of iterations
 * @param[in] tol_rel_obj convergence tolerance on the relative norm
 *   of the objective
 * @param[in] rank the rank of the approximation
 * @param[in] eta stepsize scaling parameter for variational inference
 * @param[in] adapt_engaged adaptation engaged?
 * @param[in] adapt_iterations number of iterations for eta adaptation
 * @param[in] eval_elbo evaluate ELBO every Nth iteration
 * @param[in] output_samples number of posterior samples to draw and
 *   save
 * @param[in,out] interrupt callback to be called every iteration
 * @param[in,out] logger Logger for messages
 * @param[in,out] init_writer Writer callback for unconstrained inits
 * @param[in,out] parameter_writer output for parameter values
 * @param[in,out] diagnostic_writer output for diagnostic values
 * @return error_codes::OK if successful
 */
template <class Model>
int lowrank(Model& model, const stan::io::var_context& init,
              unsigned int random_seed, unsigned int chain, double init_radius,
              int grad_samples, int elbo_samples, int max_iterations,
              double eta, int eval_window, double window_size, double rhat_cut,
              double mcse_cut, double ess_cut, int num_chains,
              bool adapt_engaged, int adapt_iterations, int output_samples,
              callbacks::interrupt& interrupt, callbacks::logger& logger,
              callbacks::writer& init_writer,
              callbacks::writer& parameter_writer,
	    callbacks::writer& diagnostic_writer, size_t rank) {
// int lowrank(Model& model, const stan::io::var_context& init,
//             unsigned int random_seed, unsigned int chain, double init_radius,
//             int grad_samples, int elbo_samples, int max_iterations,
//             double tol_rel_obj, int rank, double eta, bool adapt_engaged,
//             int adapt_iterations, int eval_elbo, int output_samples,
//             callbacks::interrupt& interrupt, callbacks::logger& logger,
//             callbacks::writer& init_writer, callbacks::writer& parameter_writer,
//             callbacks::writer& diagnostic_writer) {
  util::experimental_message(logger);

  boost::ecuyer1988 rng = util::create_rng(random_seed, chain);

  std::vector<int> disc_vector;
  std::vector<double> cont_vector = util::initialize(
      model, init, rng, init_radius, true, logger, init_writer);

  std::vector<std::string> names;
  names.push_back("lp__");
  names.push_back("log_p__");
  names.push_back("log_g__");
  names.push_back("chain_id__");
  model.constrained_param_names(names, true, true);
  parameter_writer(names);

  Eigen::VectorXd cont_params
      = Eigen::Map<Eigen::VectorXd>(&cont_vector[0], cont_vector.size(), 1);

  stan::variational::advi_lowrank<Model, boost::ecuyer1988> cmd_advi(
      model, cont_params, rng, rank, grad_samples, elbo_samples, output_samples);
  cmd_advi.run(eta, adapt_engaged, adapt_iterations,
	       max_iterations, eval_window, window_size, rhat_cut, mcse_cut,
	       ess_cut, num_chains, logger, parameter_writer, diagnostic_writer);
  // cmd_advi.run(eta, adapt_engaged, adapt_iterations, tol_rel_obj,
  //              max_iterations, logger, parameter_writer, diagnostic_writer);

  return 0;
}
}  // namespace advi
}  // namespace experimental
}  // namespace services
}  // namespace stan
#endif