// 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 2
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

/*! \file hekalib.cpp
 *  \author Christoph Schmidt-Hieber
 *  \date 2017-03-11
 *  \brief Reads Intan Technologies CLAMP data file generated by controller GUI.
 */

/*
Mostly a C++ version of read_Intan_CLP_file.m from
http://www.intantech.com/files/Intan_CLAMP_software_compiled_v1_0.zip
as of 2016-11-05
*/

#include <vector>

#include "./intanlib.h"
#include "./streams.h"
#include "../recording.h"

Segment read_segment(BinaryReader& binreader) {
    Segment segment;
    binreader >> segment.waveformNumber;
    binreader >> segment.tOffset;
    binreader >> segment.startIndex;
    binreader >> segment.endIndex;
    binreader >> segment.appliedValue;

    return segment;
}

std::vector<Segment> read_waveform(BinaryReader& binreader) {
    float interval;
    binreader >> interval;
    uint16_t numSegments;
    binreader >> numSegments;
    std::vector<Segment> waveform(numSegments);
    for (std::vector<Segment>::size_type nseg=0; nseg < waveform.size(); ++nseg) {
        waveform[nseg] = read_segment(binreader);
    }
    return waveform;
}

VoltageClampSettings read_header_voltage_clamp_settings(BinaryReader& binreader) {
    VoltageClampSettings vcSettings;
    binreader >> vcSettings.holdingVoltage;
    binreader >> vcSettings.nominalResistance;
    binreader >> vcSettings.resistance;
    binreader >> vcSettings.desiredBandwidth;
    binreader >> vcSettings.actualBandwidth;
    return vcSettings;
}

CurrentClampSettings read_header_current_clamp_settings(BinaryReader& binreader) {
    CurrentClampSettings ccSettings;
    binreader >> ccSettings.holdingCurrent;
    binreader >> ccSettings.currentStepSize;
    return ccSettings;
}

HeaderSettings read_header_settings(BinaryReader& binreader) {
    HeaderSettings hSettings;
    binreader >> hSettings.enableCapacitiveCompensation;
    binreader >> hSettings.capCompensationMagnitude;
    binreader >> hSettings.filterCutoff;
    binreader >> hSettings.pipetteOffset;
    binreader >> hSettings.samplingRate;
    binreader >> hSettings.cellRs;
    binreader >> hSettings.cellRm;
    binreader >> hSettings.cellCm;
    binreader >> hSettings.isVoltageClamp;
    binreader >> hSettings.vClampX2Mode;
    if (hSettings.isVoltageClamp > 0) {
        hSettings.vcSettings = read_header_voltage_clamp_settings(binreader);
    } else {
        hSettings.ccSettings = read_header_current_clamp_settings(binreader);
    }
    hSettings.waveform = read_waveform(binreader);

    return hSettings;
}

ChannelHeader read_one_header_channel(BinaryReader& binreader) {
    ChannelHeader hChannel;
    for (unsigned int registerIndex = 0; registerIndex < MAX_REGISTERS; ++registerIndex) {
        binreader >> hChannel.registers[registerIndex];
    }
    binreader >> hChannel.differenceAmpResidual;
    binreader >> hChannel.voltageAmpResidual;
    for (unsigned int a = 0; a < 2; a++) {
        for (unsigned int b = 0; b < 4; b++) {
            binreader >> hChannel.bestCalibration[a][b].coarse;
            binreader >> hChannel.bestCalibration[a][b].fine;
        }
    }
    for (unsigned int rIndex = 0; rIndex < 5; ++rIndex) {
        binreader >> hChannel.rFeedback[rIndex];
    }
    binreader >> hChannel.desiredBandwidth;

    return hChannel;
}

ChipHeader read_one_header_chip(BinaryReader& binreader, unsigned int numChannels) {
    ChipHeader hChip;
    hChip.channels.resize(numChannels);
    for (unsigned int nchan = 0; nchan < numChannels; ++nchan) {
        hChip.channels[nchan] = read_one_header_channel(binreader);
    }
    for (unsigned int nreg = 0; nreg < 4; ++nreg) {
        binreader >> hChip.chipRegisters[nreg];
    }

    return hChip;
}

std::vector<ChipHeader> read_header_chips(BinaryReader& binreader) {
    uint16_t numChips, numChannels;
    binreader >> numChips;
    binreader >> numChannels;
    std::vector<ChipHeader> hChips(numChips);
    for (unsigned int nchip = 0; nchip < hChips.size(); ++nchip) {
        hChips[nchip] = read_one_header_chip(binreader, numChannels);
    }
    return hChips;
}

IntanHeader read_header(BinaryReader& binreader) {
    uint32_t magic_number;
    binreader >> magic_number;
    if (magic_number != MAGIC_NUMBER) {
        throw std::runtime_error("Unrecognized file type");
    }
    IntanHeader hIntan;
    binreader >> hIntan.version_major;
    binreader >> hIntan.version_minor;
    binreader >> hIntan.datatype;

    uint16_t numBytes = 0;

    switch (hIntan.datatype) {
     case 0:
         binreader >> numBytes;
         binreader >> hIntan.date_Year;
         binreader >> hIntan.date_Month;
         binreader >> hIntan.date_Day;
         binreader >> hIntan.date_Hour;
         binreader >> hIntan.date_Minute;
         binreader >> hIntan.date_Second;
         hIntan.Chips = read_header_chips(binreader);
         hIntan.Settings = read_header_settings(binreader);
         break;
     case 1:
         binreader >> hIntan.numADCs;
         binreader >> numBytes;
         binreader >> hIntan.date_Year;
         binreader >> hIntan.date_Month;
         binreader >> hIntan.date_Day;
         binreader >> hIntan.date_Hour;
         binreader >> hIntan.date_Minute;
         binreader >> hIntan.date_Second;
         binreader >> hIntan.Settings.samplingRate;
         break;
     default:
         throw std::runtime_error("Unrecognized data type");
    }
    
    uint64_t pos = binreader.currentPos();
    if (pos != numBytes) {
        throw std::runtime_error("Header NumBytes doesn't match number of bytes");
    }

    return hIntan;
}

std::vector<std::vector<float> > read_data(BinaryReader& binreader, const IntanHeader& hIntan) {
    uint64_t bytes_remaining = binreader.bytesRemaining();
    uint64_t length = bytes_remaining / (4+4+4+4);
    std::vector<uint32_t> timestamps(length);
    std::vector<float> applied(length);
    std::vector<float> totalClamp(length);
    std::vector<std::vector<float> > channels(2);
    channels[0].resize(length);
    channels[1].resize(length);
    for (unsigned int idata = 0; idata < length; ++idata) {
        binreader >> timestamps[idata];
        binreader >> applied[idata]; 
        binreader >> channels[1][idata];
        binreader >> channels[0][idata];
        constexpr float vfactor = 1e3f; // V -> mV
        constexpr float ifactor = 1e12f; // A -> pA
        if (hIntan.Settings.isVoltageClamp) {
            channels[0][idata] *= ifactor;
            channels[1][idata] *= vfactor;
        } else {
            channels[1][idata] *= ifactor;
            channels[0][idata] *= vfactor;
        }
    }

    return channels;
}

std::vector<std::vector<float> > read_aux_data(BinaryReader& binreader, uint16_t numADCs) {
    uint64_t bytes_remaining = binreader.bytesRemaining();
    uint64_t length = bytes_remaining / (4+2+2+2*numADCs);
    std::vector<uint32_t> timestamps(length);
    std::vector<uint16_t> digitalIn(length);
    std::vector<uint16_t> digitalOut(length);
    std::vector<std::vector<float> > adc(numADCs);
    for (unsigned int iadc = 0; iadc < numADCs; ++iadc) {
        adc[iadc].resize(length);
    }
    for (unsigned int idata = 0; idata < length; ++idata) {
        binreader >> timestamps[idata];
        binreader >> digitalIn[idata]; 
        binreader >> digitalOut[idata];
        for (unsigned int iadc = 0; iadc < numADCs; ++iadc) {
            uint16_t tmpui;
            binreader >> tmpui;
            adc[iadc][idata] = static_cast<float>(tmpui * 0.0003125 - (1 << 15));
        }
    }

    return adc;
}

void stfio::importIntanFile(const std::string &fName, Recording &ReturnData, ProgressInfo& /*progDlg*/) {
    unique_ptr<FileInStream> fs(new FileInStream());

    std::string wfName(fName);

    fs->open(wfName);

    unique_ptr<BinaryReader> binreader(new BinaryReader(std::move(fs)));

    IntanHeader hIntan = read_header(*binreader);
    if (hIntan.datatype == 0) {
        std::vector<std::vector<float> > channels = read_data(*binreader, hIntan);
        ReturnData.resize(channels.size());
        ReturnData.SetXScale(1e3/hIntan.Settings.samplingRate);
        ReturnData.SetXUnits("ms");
        int mon = hIntan.date_Month-1;
        int year = hIntan.date_Year - 1900;
        ReturnData.SetDateTime(year, mon, hIntan.date_Day,
                               hIntan.date_Hour, hIntan.date_Minute, hIntan.date_Second);
        for (unsigned int nchan = 0; nchan < channels.size(); ++nchan) {
            // ReturnData[nchan].resize(hIntan.Settings.waveform.size());
            ReturnData[nchan].resize(1);
        }
        if (hIntan.Settings.isVoltageClamp) {
            ReturnData[0].SetYUnits("pA");
            ReturnData[1].SetYUnits("mV");
        } else {
            ReturnData[1].SetYUnits("pA");
            ReturnData[0].SetYUnits("mV");
        }
        unsigned int nsec = 0;
        for (unsigned int nchan = 0; nchan < channels.size(); ++nchan) {
            ReturnData[nchan][nsec].resize(channels[nchan].size());
            std::copy(channels[nchan].begin(), channels[nchan].end(),
                      ReturnData[nchan][nsec].get_w().begin());
        }

        // for (std::vector<Segment>::const_iterator it = hIntan.Settings.waveform.begin();
        //      it != hIntan.Settings.waveform.end();
        //      ++it)
        // {
        //     for (unsigned int nchan = 0; nchan < channels.size(); ++nchan) {
        //         ReturnData[nchan][nsec].resize(it->endIndex - it->startIndex);
        //         std::copy(&(channels[nchan][it->startIndex]), &(channels[nchan][it->endIndex]),
        //                   ReturnData[nchan][nsec].get_w().begin());
        //     }
        //     nsec++;
        // }

    } else {
        std::vector<std::vector<float> > aux_data = read_aux_data(*binreader, hIntan.numADCs);
        ReturnData.resize(1);
        ReturnData[0].resize(1);
        ReturnData[0][0].resize(aux_data[0].size());
        std::copy(aux_data[0].begin(), aux_data[0].end(), ReturnData[0][0].get_w().begin());
    }

}
