#include "catch.hpp"

#include "ClientSocket.h"
#include "logger.h"
#include "multiSlsDetector.h"
#include "slsDetector.h"
#include "sls_detector_defs.h"

#include "Timer.h"
#include "sls_detector_funcs.h"
#include <iostream>
#include <vector>
#define VERBOSE

// Header holding all configurations for different detectors
#include "tests/config.h"
#include "tests/globals.h"
// using namespace test;
// using dt = slsDetectorDefs::detectorType;
// extern std::string hostname;
// extern std::string detector_type;
// extern dt type;

TEST_CASE("Single detector no receiver", "[.integration][.single]") {
    auto t = slsDetector::getTypeFromDetector(test::hostname);
    CHECK(t == test::type);

    slsDetector d(t);
    CHECK(d.getDetectorTypeAsEnum() == t);
    CHECK(d.getDetectorTypeAsString() == test::detector_type);

    d.setHostname(test::hostname);
    CHECK(d.getHostname() == test::hostname);

    CHECK(d.setDetectorType() == test::type);

    d.freeSharedMemory();
}

TEST_CASE("Set control port then create a new object with this control port",
          "[.integration][.single]") {
    /*
    TODO!
    Standard port but should not be hardcoded
    Is this the best way to initialize the detectors
    Using braces to make the object go out of scope
    */
    int old_cport = DEFAULT_PORTNO;
    int old_sport = DEFAULT_PORTNO + 1;
    int new_cport = 1993;
    int new_sport = 2000;
    {
        slsDetector d(test::type);
        d.setHostname(test::hostname);
        CHECK(d.getControlPort() == old_cport);
        d.setControlPort(new_cport);
        CHECK(d.getStopPort() == old_sport);
        d.setStopPort(new_sport);
        d.freeSharedMemory();
    }
    {
        slsDetector d(test::type);
        d.setHostname(test::hostname);
        d.setControlPort(new_cport);
        d.setStopPort(new_sport);
        CHECK(d.getControlPort() == new_cport);
        CHECK(d.getStopPort() == new_sport);

        // Reset standard ports
        d.setControlPort(old_cport);
        d.setStopPort(old_sport);
        d.freeSharedMemory();
    }

    slsDetector d(test::type);
    d.setHostname(test::hostname);
    CHECK(d.getStopPort() == DEFAULT_PORTNO + 1);
    d.freeSharedMemory();
}


TEST_CASE("single EIGER detector no receiver basic set and get",
          "[.integration][eiger]") {
    // TODO! this test should take command line arguments for config
    SingleDetectorConfig c;

    // Read type by connecting to the detector
    auto type = slsDetector::getTypeFromDetector(c.hostname);
    CHECK(type == c.type_enum);

    // Create slsDetector of said type and set hostname and detector online
    slsDetector d(type);
    CHECK(d.getDetectorTypeAsEnum() == type);
    CHECK(d.getDetectorTypeAsString() == c.type_string);

    d.setHostname(c.hostname);
    CHECK(d.getHostname() == c.hostname);

    CHECK(d.getUseReceiverFlag() == false);
    CHECK_NOTHROW(d.checkDetectorVersionCompatibility());

    // Setting and reading exposure time
    auto t = 1000000000;
    d.setExptime(t);
    CHECK(d.getExptime() == t);

    // size of an eiger half module with and without gap pixels
    CHECK(d.getTotalNumberOfChannels() == 256 * 256 * 4);
    CHECK(d.getTotalNumberOfChannels(slsDetectorDefs::dimension::X) == 1024);
    CHECK(d.getTotalNumberOfChannels(slsDetectorDefs::dimension::Y) == 256);
    // CHECK(d.getTotalNumberOfChannels(slsDetectorDefs::dimension::Z) == 1);
    CHECK(d.getTotalNumberOfChannelsInclGapPixels(
              slsDetectorDefs::dimension::X) == 1024);
    CHECK(d.getTotalNumberOfChannelsInclGapPixels(
              slsDetectorDefs::dimension::Y) == 256);
    // CHECK(d.getTotalNumberOfChannelsInclGapPixels(slsDetectorDefs::dimension::Z)
    // == 1);

    CHECK(d.getNChans() == 256 * 256);
    CHECK(d.getNChans(slsDetectorDefs::dimension::X) == 256);
    CHECK(d.getNChans(slsDetectorDefs::dimension::Y) == 256);
    // CHECK(d.getNChans(slsDetectorDefs::dimension::Z) == 1);

    CHECK(d.getNChips() == 4);
    CHECK(d.getNChips(slsDetectorDefs::dimension::X) == 4);
    CHECK(d.getNChips(slsDetectorDefs::dimension::Y) == 1);
    // CHECK(d.getNChips(slsDetectorDefs::dimension::Z) == 1);

    d.freeSharedMemory();
}



TEST_CASE("Locking mechanism and last ip", "[.integration][.single]") {
    slsDetector d(test::type);
    d.setHostname(test::hostname);

    // Check that detector server is unlocked then lock
    CHECK(d.lockServer() == 0);
    d.lockServer(1);
    CHECK(d.lockServer() == 1);

    // Can we still access the detector while it's locked
    auto t = 1300000000;
    d.setExptime(t);
    CHECK(d.getExptime() == t);

    // unlock again and free
    d.lockServer(0);
    CHECK(d.lockServer() == 0);

    CHECK(d.getLastClientIP() == test::my_ip);
    d.freeSharedMemory();
}

TEST_CASE("Set settings", "[.integration][.single]"){
    slsDetector d(test::type);
    d.setHostname(test::hostname);
    CHECK(d.setSettings(defs::STANDARD) == defs::STANDARD);
}


TEST_CASE("Timer functions", "[.integration][cli]") {
    // FRAME_NUMBER, /**< number of real time frames: total number of
    // acquisitions is number or frames*number of triggers */ ACQUISITION_TIME,
    // /**< exposure time */ FRAME_PERIOD, /**< period between exposures */
    // DELAY_AFTER_TRIGGER, /**< delay between trigger and start of exposure or
    // readout (in triggered mode) */ GATES_NUMBER, /**< number of gates per
    // frame (in gated mode) */ TRIGGER_NUMBER, /**< number of triggers: total
    // number of acquisitions is number or frames*number of triggers */
    // ACTUAL_TIME, /**< Actual time of the detector's internal timer */
    // MEASUREMENT_TIME,  /**< Time of the measurement from the detector (fifo)
    // */

    // PROGRESS, /**< fraction of measurement elapsed - only get! */
    // MEASUREMENTS_NUMBER,
    // FRAMES_FROM_START,
    // FRAMES_FROM_START_PG,
    // SAMPLES,
    // SUBFRAME_ACQUISITION_TIME, /**< subframe exposure time */
    // STORAGE_CELL_NUMBER, /**<number of storage cells */
    // SUBFRAME_DEADTIME, /**< subframe deadtime */
    // MEASURED_PERIOD,	/**< measured period */
    // MEASURED_SUBPERIOD,	/**< measured subperiod */
    // MAX_TIMERS

    slsDetector d(test::type);
    d.setHostname(test::hostname);

    // Number of frames
    auto frames = 5;
    d.setNumberOfFrames(frames);
    CHECK(d.getNumberOfFrames() == frames);

    auto exptime = 2000000000;
    d.setExptime(exptime);
    CHECK(d.getExptime() == exptime);

    auto period = 2000000000;
    d.setPeriod(period);
    CHECK(d.getPeriod() == period);

    if (test::type != dt::EIGER) {
        auto delay = 10000;
        d.setDelayAfterTrigger(delay);
        CHECK(d.getDelayAfterTrigger() ==
              delay);
    }

    auto triggers = 2;
    d.setNumberOfTriggers(triggers);
    CHECK(d.getNumberOfTriggers() == triggers);

    if (test::type == dt::EIGER) {
        auto subtime = 200;
        d.setSubExptime(subtime);
        CHECK(d.getSubExptime() == subtime);
    }
    // for (int i =0; i!=frames; ++i)
        d.startAndReadAll();

    d.freeSharedMemory();

}

// TEST_CASE("Aquire", "[.integration][eiger]"){
//     SingleDetectorConfig c;
//     auto type = slsDetector::getTypeFromDetector(c.hostname);
//     slsDetector d(type);
//     d.setHostname(c.hostname);

//     auto period = 1000000000;
//     auto exptime = 100000000;
//     d.setNumberOfFrames(5);
//     d.setExptime(exptime);
//     d.setPeriod(period);
//     d.startAndReadAll();

//     auto rperiod =
//     d.getMeasuredPeriod();
//     CHECK(rperiod == 0.1);

//     d.freeSharedMemory();
// }

TEST_CASE(
    "Eiger Dynamic Range with effect on rate correction and clock divider",
    "[.eigerintegration]") {
    SingleDetectorConfig c;

    int ratecorr = 125;

    // pick up multi detector from shm id 0
    multiSlsDetector m(0);

    // ensure eiger detector type, hostname and online
    REQUIRE(m.getDetectorTypeAsEnum() == c.type_enum);
    REQUIRE(m.getHostname() == c.hostname);

    // starting state with rate correction off
    m.setRateCorrection(0);

    // dr 16: clk divider, no change for ratecorr
    CHECK(m.setDynamicRange(16) == 16);
    CHECK(m.setSpeed(slsDetectorDefs::CLOCK_DIVIDER) == 1);
    CHECK(m.getRateCorrection() == 0);

    // dr 32: clk divider, no change for ratecorr
    CHECK(m.setDynamicRange(32) == 32);
    CHECK(m.setSpeed(slsDetectorDefs::CLOCK_DIVIDER) == 2);
    CHECK(m.getRateCorrection() == 0);

    // other drs: no change for clk divider, no change for ratecorr
    CHECK(m.setDynamicRange(8) == 8);
    CHECK(m.setSpeed(slsDetectorDefs::CLOCK_DIVIDER) == 2);
    CHECK(m.getRateCorrection() == 0);
    CHECK(m.setDynamicRange(4) == 4);
    CHECK(m.setSpeed(slsDetectorDefs::CLOCK_DIVIDER) == 2);
    CHECK(m.getRateCorrection() == 0);

    // switching on rate correction with dr 16, 32
    m.setDynamicRange(16);
    m.setRateCorrection(ratecorr);
    CHECK(m.getRateCorrection() == ratecorr);
    m.setDynamicRange(32);
    CHECK(m.getRateCorrection() == ratecorr);

    // ratecorr fail with dr 4 or 8
    CHECK_THROWS_AS(m.setDynamicRange(8), sls::RuntimeError);
    CHECK(m.getRateCorrection() == 0);
    m.setDynamicRange(16);
    m.setDynamicRange(16);
    m.setRateCorrection(ratecorr);
    m.setDynamicRange(16);
    m.setRateCorrection(ratecorr);
    CHECK_THROWS_AS(m.setDynamicRange(4), sls::RuntimeError);
    CHECK(m.getRateCorrection() == 0);
}

TEST_CASE("Chiptestboard Loading Patterns", "[.ctbintegration]") {
    SingleDetectorConfig c;

    // pick up multi detector from shm id 0
    multiSlsDetector m(0);

    // ensure ctb detector type, hostname and online
    REQUIRE(m.getDetectorTypeAsEnum() == c.type_enum);
    REQUIRE(m.getHostname() == c.hostname);

    uint64_t word = 0;
    int addr = 0;
    int level = 0;
    const int MAX_ADDR = 0x7fff;

    word = 0xc000000000f47ff;
    CHECK(m.setPatternIOControl(word) == word);
    CHECK(m.setPatternIOControl(-1) == word);
    CHECK(m.setPatternIOControl(0) == 0);

    CHECK(m.setPatternClockControl(word) == word);
    CHECK(m.setPatternClockControl(-1) == word);
    CHECK(m.setPatternClockControl(0) == 0);

    // testing pattern word will execute the pattern as well
    addr = 0;
    m.setPatternWord(addr, word);
    CHECK(m.setPatternWord(addr, -1) == word);
    addr = MAX_ADDR - 1;
    m.setPatternWord(addr, word);
    CHECK(m.setPatternWord(addr, -1) == word);
    addr = 0x2FF;
    m.setPatternWord(addr, word);
    CHECK(m.setPatternWord(addr, -1) == word);
    addr = MAX_ADDR;
    CHECK_THROWS_AS(m.setPatternWord(addr, word), sls::RuntimeError);
    CHECK_THROWS_WITH(m.setPatternWord(addr, word),
                      Catch::Matchers::Contains("be between 0 and"));
    addr = -1;
    CHECK_THROWS_AS(m.setPatternWord(addr, word), sls::RuntimeError);
    CHECK_THROWS_WITH(m.setPatternWord(addr, word),
                      Catch::Matchers::Contains("be between 0 and"));

    addr = 0x2FF;
    for (level = 0; level < 3; ++level) {
        CHECK(m.setPatternWaitAddr(level, addr) == addr);
        CHECK(m.setPatternWaitAddr(level, -1) == addr);
    }
    CHECK_THROWS_WITH(m.setPatternWaitAddr(-1, addr),
                      Catch::Matchers::Contains("be between 0 and"));
    CHECK_THROWS_WITH(m.setPatternWaitAddr(0, MAX_ADDR),
                      Catch::Matchers::Contains("be between 0 and"));

    for (level = 0; level < 3; ++level) {
        CHECK(m.setPatternWaitTime(level, word) == word);
        CHECK(m.setPatternWaitTime(level, -1) == word);
    }
    CHECK_THROWS_WITH(m.setPatternWaitTime(-1, word),
                      Catch::Matchers::Contains("be between 0 and"));

    {
        int startaddr = addr;
        int stopaddr = addr + 5;
        int nloops = 2;
        for (level = 0; level < 3; ++level) {
            m.setPatternLoops(level, startaddr, stopaddr, nloops);
            auto r = m.getPatternLoops(level);
            CHECK(r[0] == startaddr);
            CHECK(r[1] == stopaddr);
            CHECK(r[2] == nloops);
        }
        m.setPatternLoops(-1, startaddr, stopaddr, nloops);
        auto r = m.getPatternLoops(-1);
        CHECK(r[0] == startaddr);
        CHECK(r[1] == stopaddr);
        CHECK(r[2] == -1);

        CHECK_THROWS_WITH(m.setPatternLoops(-1, startaddr, MAX_ADDR, nloops),
                          Catch::Matchers::Contains("be less than"));
        CHECK_THROWS_WITH(m.setPatternLoops(-1, MAX_ADDR, stopaddr, nloops),
                          Catch::Matchers::Contains("be less than"));
    }
}


TEST_CASE("Chiptestboard Dbit offset, list, sampling, advinvert", "[.ctbintegration][dbit]") {
    SingleDetectorConfig c;

    // pick up multi detector from shm id 0
    multiSlsDetector m(0);

    // ensure ctb detector type, hostname and online
    REQUIRE(m.getDetectorTypeAsEnum() == c.type_enum);
    REQUIRE(m.getHostname() == c.hostname);

    // dbit offset
    m.setReceiverDbitOffset(0);
    CHECK(m.getReceiverDbitOffset() == 0);
    m.setReceiverDbitOffset(-1);
    CHECK(m.getReceiverDbitOffset() == 0);
    m.setReceiverDbitOffset(5);
    CHECK(m.getReceiverDbitOffset() == 5);

    // dbit list

    std::vector <int> list = m.getReceiverDbitList();
    list.clear();
    for (int i = 0; i < 10; ++i)
        list.push_back(i);
    m.setReceiverDbitList(list);
    
    CHECK(m.getReceiverDbitList().size() == 10);

    list.push_back(64);
    CHECK_THROWS_AS(m.setReceiverDbitList(list), sls::RuntimeError);
    CHECK_THROWS_WITH(m.setReceiverDbitList(list), 
        Catch::Matchers::Contains("be between 0 and 63"));

    list.clear();
    for (int i = 0; i < 65; ++i)
        list.push_back(i);
    CHECK(list.size() == 65);
    CHECK_THROWS_WITH(m.setReceiverDbitList(list),  
        Catch::Matchers::Contains("be greater than 64"));

    list.clear(); 
    m.setReceiverDbitList(list);
    CHECK(m.getReceiverDbitList().empty());

    // adcinvert
    m.setADCInvert(0);
    CHECK(m.getADCInvert() == 0);
    m.setADCInvert(5);
    CHECK(m.getADCInvert() == 5);
    m.setADCInvert(-1);
    CHECK(m.getADCInvert() == -1);

    // ext sampling reg
    m.setExternalSamplingSource(0);
    CHECK(m.getExternalSamplingSource() == 0);
    m.setExternalSamplingSource(62);
    CHECK(m.getExternalSamplingSource() == 62);
    CHECK_THROWS_WITH(m.setExternalSamplingSource(64),
         Catch::Matchers::Contains("be 0-63"));
    CHECK(m.getExternalSamplingSource() == 62); 
    m.setExternalSampling(1);
    CHECK(m.getExternalSampling() == 1);
    m.setExternalSampling(0);
    CHECK(m.getExternalSampling() == 0);
    m.setExternalSampling(1);
    CHECK(m.getExternalSampling() == 1);
    CHECK(m.readRegister(0x7b) == 0x1003E);
    
}

TEST_CASE("Eiger or Jungfrau startingfnum", "[.eigerintegration][.jungfrauintegration][startingfnum]") {
    SingleDetectorConfig c;

    // pick up multi detector from shm id 0
    multiSlsDetector m(0);

    // ensure ctb detector type, hostname and online
    REQUIRE(((m.getDetectorTypeAsEnum() == slsDetectorDefs::detectorType::EIGER) || (m.getDetectorTypeAsEnum() == slsDetectorDefs::detectorType::JUNGFRAU)));
    REQUIRE(m.getHostname() == c.hostname);

     CHECK(m.setNumberOfFrames(1) == 1);

    // starting fnum
    uint64_t val = 8;

    m.setStartingFrameNumber(val);
    CHECK(m.getStartingFrameNumber() == val);
    CHECK(m.acquire() == slsDetectorDefs::OK);
    CHECK(m.getReceiverCurrentFrameIndex() == val);

    ++val;
    CHECK(m.acquire() == slsDetectorDefs::OK);
    CHECK(m.getReceiverCurrentFrameIndex() == val);

    CHECK_THROWS_AS(m.setStartingFrameNumber(0), sls::RuntimeError);

    if (m.getDetectorTypeAsString() == "Eiger") {
        val = 281474976710655;
    } else if (m.getDetectorTypeAsString() == "Jungfrau") {
        val = 18446744073709551615;
    }
    m.setStartingFrameNumber(val);
    CHECK(m.getStartingFrameNumber() == val);
    CHECK(m.acquire() == slsDetectorDefs::OK);
    CHECK(m.getReceiverCurrentFrameIndex() == val);
    CHECK(m.getStartingFrameNumber() == (val + 1));
}

TEST_CASE("Eiger readnlines", "[.eigerintegration][readnlines]") {
    SingleDetectorConfig c;

    // pick up multi detector from shm id 0
    multiSlsDetector m(0);

    // ensure detector type, hostname
    REQUIRE((m.getDetectorTypeAsEnum() == slsDetectorDefs::detectorType::EIGER));
    REQUIRE(m.getHostname() == c.hostname);

    m.setDynamicRange(16);
    m.enableTenGigabitEthernet(0);
    m.setReadNLines(256);
    CHECK(m.getReadNLines() == 256);
    m.setReadNLines(1);
    CHECK(m.getReadNLines() == 1);
        
    m.setDynamicRange(8);
    m.setReadNLines(256);
    CHECK(m.getReadNLines() == 256);
    CHECK_THROWS_AS(m.setReadNLines(1), sls::RuntimeError);
    CHECK(m.getReadNLines() == 256);
    CHECK_THROWS_AS(m.setReadNLines(0), sls::RuntimeError);
    m.setReadNLines(256);
}