//
// Copyright 2010 Ettus Research LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see .
//
#include 
#include 
#include 
#include "usrp2_impl.hpp"
using namespace uhd;
using namespace uhd::usrp;
namespace asio = boost::asio;
/***********************************************************************
 * Constants
 **********************************************************************/
typedef std::complex fc32_t;
typedef std::complex sc16_t;
static const float float_scale_factor = pow(2.0, 15);
//max length with header, stream id, seconds, fractional seconds
static const size_t max_vrt_header_words = 5;
/***********************************************************************
 * Helper Functions
 **********************************************************************/
static inline void host_floats_to_usrp2_shorts(
    short *usrp2_shorts,
    const float *host_floats,
    size_t num_samps
){
    for(size_t i = 0; i < num_samps; i++){
        usrp2_shorts[i] = htons(short(host_floats[i]*float_scale_factor));
    }
}
static inline void usrp2_shorts_to_host_floats(
    float *host_floats,
    const short *usrp2_shorts,
    size_t num_samps
){
    for(size_t i = 0; i < num_samps; i++){
        host_floats[i] = float(short(ntohs(usrp2_shorts[i])))/float_scale_factor;
    }
}
static inline void host_shorts_to_usrp2_shorts(
    short *usrp2_shorts,
    const short *host_shorts,
    size_t num_samps
){
    for(size_t i = 0; i < num_samps; i++){
        usrp2_shorts[i] = htons(host_shorts[i]);
    }
}
static inline void usrp2_shorts_to_host_shorts(
    short *host_shorts,
    const short *usrp2_shorts,
    size_t num_samps
){
    for(size_t i = 0; i < num_samps; i++){
        host_shorts[i] = ntohs(usrp2_shorts[i]);
    }
}
/***********************************************************************
 * Send Raw Data
 **********************************************************************/
size_t usrp2_impl::send_raw(
    const boost::asio::const_buffer &buff,
    const uhd::metadata_t &metadata
){
    std::vector buffs(2);
    uint32_t vrt_hdr[max_vrt_header_words];
    uint32_t vrt_hdr_flags = 0;
    size_t num_vrt_hdr_words = 1;
    //load the vrt header and flags
    if(metadata.has_stream_id){
        vrt_hdr_flags |= (0x1 << 28); //IF Data packet with Stream Identifier
        vrt_hdr[num_vrt_hdr_words++] = htonl(metadata.stream_id);
    }
    if(metadata.has_time_spec){
        vrt_hdr_flags |= (0x3 << 22) | (0x1 << 20); //TSI: Other, TSF: Sample Count Timestamp
        vrt_hdr[num_vrt_hdr_words++] = htonl(metadata.time_spec.secs);
        vrt_hdr[num_vrt_hdr_words++] = htonl(metadata.time_spec.ticks);
        vrt_hdr[num_vrt_hdr_words++] = 0; //unused part of fractional seconds
    }
    vrt_hdr_flags |= (metadata.start_of_burst)? (0x1 << 25) : 0;
    vrt_hdr_flags |= (metadata.end_of_burst)?   (0x1 << 24) : 0;
    num_vrt_hdr_words += asio::buffer_size(buff)/sizeof(uint32_t);
    //fill in complete header word
    vrt_hdr[0] = htonl(vrt_hdr_flags |
        ((_stream_id_to_packet_seq[metadata.stream_id]++ & 0xf) << 16) |
        (num_vrt_hdr_words & 0xffff)
    );
    //load the buffer vector
    size_t vrt_hdr_size = num_vrt_hdr_words*sizeof(uint32_t);
    buffs[0] = asio::buffer(&vrt_hdr, vrt_hdr_size);
    buffs[1] = buff;
    //send and return number of samples
    return (_data_transport->send(buffs) - vrt_hdr_size)/sizeof(sc16_t);
}
/***********************************************************************
 * Receive Raw Data
 **********************************************************************/
size_t usrp2_impl::recv_raw(
    const boost::asio::mutable_buffer &buff,
    uhd::metadata_t &metadata
){
    //handle the case where there is spillover
    if (asio::buffer_size(_splillover_buff) != 0){
        size_t bytes_to_copy = std::min(
            asio::buffer_size(_splillover_buff),
            asio::buffer_size(buff)
        );
        std::memcpy(
            asio::buffer_cast(buff),
            asio::buffer_cast(_splillover_buff),
            bytes_to_copy
        );
        _splillover_buff = asio::buffer(
            asio::buffer_cast(_splillover_buff)+bytes_to_copy,
            asio::buffer_size(_splillover_buff)-bytes_to_copy
        );
        //std::cout << boost::format("Copied spillover %d samples") % (bytes_to_copy/sizeof(sc16_t)) << std::endl;
        return bytes_to_copy/sizeof(sc16_t);
    }
    //load the buffer vector
    std::vector buffs(3);
    uint32_t vrt_hdr[max_vrt_header_words];
    buffs[0] = asio::buffer(vrt_hdr, max_vrt_header_words*sizeof(uint32_t));
    buffs[1] = asio::buffer(//make sure its on a word boundary
        buff, asio::buffer_size(buff) & ~(sizeof(uint32_t) - 1)
    );
    buffs[2] = asio::buffer(_spillover_mem, _mtu);
    //receive into the buffers
    size_t bytes_recvd = _data_transport->recv(buffs);
    //failure case
    if (bytes_recvd < max_vrt_header_words*sizeof(uint32_t)) return 0;
    //unpack the vrt header
    metadata = uhd::metadata_t();
    uint32_t vrt_header = ntohl(vrt_hdr[0]);
    metadata.has_stream_id = true;
    metadata.stream_id = ntohl(vrt_hdr[1]);
    metadata.has_time_spec = true;
    metadata.time_spec.secs = ntohl(vrt_hdr[2]);
    metadata.time_spec.ticks = ntohl(vrt_hdr[3]);
    //extract the number of bytes received
    size_t num_words = (vrt_header & 0xffff) - max_vrt_header_words;
    size_t num_bytes = num_words*sizeof(uint32_t);
    //handle the case where spillover memory was used
    size_t spillover_size = num_bytes - std::min(num_bytes, asio::buffer_size(buff));
    _splillover_buff = asio::buffer(_spillover_mem, spillover_size);
    return (num_bytes - spillover_size)/sizeof(sc16_t);
}
/***********************************************************************
 * Send Data
 **********************************************************************/
size_t usrp2_impl::send(
    const boost::asio::const_buffer &buff,
    const uhd::metadata_t &metadata,
    const std::string &type
){
    if (type == "32fc"){
        size_t num_samps = asio::buffer_size(buff)/sizeof(fc32_t);
        boost::shared_array raw_mem(new sc16_t[num_samps]);
        boost::asio::mutable_buffer raw_buff(raw_mem.get(), num_samps*sizeof(sc16_t));
        host_floats_to_usrp2_shorts(
            asio::buffer_cast(raw_buff),
            asio::buffer_cast(buff),
            num_samps*2 //double for complex
        );
        return send_raw(raw_buff, metadata);
    }
    if (type == "16sc"){
        #ifdef HAVE_BIG_ENDIAN
        return send_raw(buff, metadata);
        #else
        size_t num_samps = asio::buffer_size(buff)/sizeof(sc16_t);
        boost::shared_array raw_mem(new sc16_t[num_samps]);
        boost::asio::mutable_buffer raw_buff(raw_mem.get(), num_samps*sizeof(sc16_t));
        host_shorts_to_usrp2_shorts(
            asio::buffer_cast(raw_buff),
            asio::buffer_cast(buff),
            num_samps*2 //double for complex
        );
        return send_raw(raw_buff, metadata);
        #endif
    }
    throw std::runtime_error(str(boost::format("usrp2 send: cannot handle type \"%s\"") % type));
}
/***********************************************************************
 * Receive Data
 **********************************************************************/
size_t usrp2_impl::recv(
    const boost::asio::mutable_buffer &buff,
    uhd::metadata_t &metadata,
    const std::string &type
){
    if (type == "32fc"){
        size_t num_samps = asio::buffer_size(buff)/sizeof(fc32_t);
        boost::shared_array raw_mem(new sc16_t[num_samps]);
        boost::asio::mutable_buffer raw_buff(raw_mem.get(), num_samps*sizeof(sc16_t));
        num_samps = recv_raw(raw_buff, metadata);
        usrp2_shorts_to_host_floats(
            asio::buffer_cast(buff),
            asio::buffer_cast(raw_buff),
            num_samps*2 //double for complex
        );
        return num_samps;
    }
    if (type == "16sc"){
        #ifdef HAVE_BIG_ENDIAN
        return recv_raw(buff, metadata);
        #else
        size_t num_samps = asio::buffer_size(buff)/sizeof(sc16_t);
        boost::shared_array raw_mem(new sc16_t[num_samps]);
        boost::asio::mutable_buffer raw_buff(raw_mem.get(), num_samps*sizeof(sc16_t));
        num_samps = recv_raw(raw_buff, metadata);
        usrp2_shorts_to_host_shorts(
            asio::buffer_cast(buff),
            asio::buffer_cast(raw_buff),
            num_samps*2 //double for complex
        );
        return num_samps;
        #endif
    }
    throw std::runtime_error(str(boost::format("usrp2 recv: cannot handle type \"%s\"") % type));
}