/**
 * @file TriggerEngineGTest.cpp
 * @brief Unit tests for the StreamHub hub-side trigger FSM.
 *
 * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
 * the Development of Fusion Energy ('Fusion for Energy').
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved
 * by the European Commission - subsequent versions of the EUPL (the "Licence")
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
 *
 * @warning Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an "AS IS"
 * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the Licence permissions and limitations under the Licence.
 */

#include "TriggerEngine.h"

#include <gtest/gtest.h>

using namespace StreamHub;
using MARTe::float64;

static TriggerConfig MakeConfig(TrigEdge edge, float64 threshold,
                                float64 windowSec, float64 prePercent,
                                TrigAcqMode mode = kTrigNormal) {
    TriggerConfig cfg;
    cfg.signalKey  = "src:sig";
    cfg.edge       = edge;
    cfg.threshold  = threshold;
    cfg.windowSec  = windowSec;
    cfg.prePercent = prePercent;
    cfg.mode       = mode;
    return cfg;
}

TEST(TriggerEngineGTest, TestInitialState) {
    TriggerEngine eng;
    EXPECT_EQ(kTrigIdle, eng.GetState());
    EXPECT_FALSE(eng.GetStopped());
    float64 tt, pre, post;
    EXPECT_FALSE(eng.GetFiredWindow(tt, pre, post));
}

TEST(TriggerEngineGTest, TestIdleIgnoresSamples) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 1.0, 20.0));
    eng.CheckSample(1.0, 0.0);
    eng.CheckSample(2.0, 1.0); /* would fire if armed */
    EXPECT_EQ(kTrigIdle, eng.GetState());
}

TEST(TriggerEngineGTest, TestRisingEdgeFires) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 1.0, 20.0));
    eng.Arm();
    EXPECT_EQ(kTrigArmed, eng.GetState());
    eng.CheckSample(1.0, 0.0);  /* primes prevValue */
    eng.CheckSample(2.0, 1.0);  /* 0.0 < 0.5 <= 1.0 → fire */
    EXPECT_EQ(kTrigCollecting, eng.GetState());

    float64 tt, pre, post;
    ASSERT_TRUE(eng.GetFiredWindow(tt, pre, post));
    EXPECT_DOUBLE_EQ(2.0, tt);
    EXPECT_DOUBLE_EQ(0.2, pre);   /* 1.0 s × 20 % */
    EXPECT_DOUBLE_EQ(0.8, post);
}

TEST(TriggerEngineGTest, TestFirstSampleNeverFires) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 1.0, 20.0));
    eng.Arm();
    /* First sample above threshold must only prime the detector */
    eng.CheckSample(1.0, 2.0);
    EXPECT_EQ(kTrigArmed, eng.GetState());
}

TEST(TriggerEngineGTest, TestFallingEdgeFires) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeFalling, 0.5, 1.0, 50.0));
    eng.Arm();
    eng.CheckSample(1.0, 1.0);
    eng.CheckSample(2.0, 1.5);  /* rising: must not fire */
    EXPECT_EQ(kTrigArmed, eng.GetState());
    eng.CheckSample(3.0, 0.2);  /* 1.5 > 0.5 >= 0.2 → fire */
    EXPECT_EQ(kTrigCollecting, eng.GetState());

    float64 tt, pre, post;
    ASSERT_TRUE(eng.GetFiredWindow(tt, pre, post));
    EXPECT_DOUBLE_EQ(3.0, tt);
    EXPECT_DOUBLE_EQ(0.5, pre);
    EXPECT_DOUBLE_EQ(0.5, post);
}

TEST(TriggerEngineGTest, TestBothEdgesFire) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeBoth, 0.0, 1.0, 0.0));
    eng.Arm();
    eng.CheckSample(1.0, -1.0);
    eng.CheckSample(2.0, 1.0);  /* rising through 0 */
    EXPECT_EQ(kTrigCollecting, eng.GetState());

    eng.Arm();
    eng.CheckSample(3.0, 1.0);
    eng.CheckSample(4.0, -1.0); /* falling through 0 */
    EXPECT_EQ(kTrigCollecting, eng.GetState());
}

TEST(TriggerEngineGTest, TestNoFireBelowThreshold) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 5.0, 1.0, 20.0));
    eng.Arm();
    eng.CheckSample(1.0, 0.0);
    eng.CheckSample(2.0, 1.0);
    eng.CheckSample(3.0, 4.9);
    EXPECT_EQ(kTrigArmed, eng.GetState());
}

TEST(TriggerEngineGTest, TestConfigClamping) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.0, 100.0, 150.0));
    TriggerConfig cfg = eng.GetConfig();
    EXPECT_DOUBLE_EQ(10.0, cfg.windowSec);
    EXPECT_DOUBLE_EQ(100.0, cfg.prePercent);

    eng.SetConfig(MakeConfig(kEdgeRising, 0.0, 1.0e-6, -5.0));
    cfg = eng.GetConfig();
    EXPECT_DOUBLE_EQ(1.0e-4, cfg.windowSec);
    EXPECT_DOUBLE_EQ(0.0, cfg.prePercent);
}

TEST(TriggerEngineGTest, TestConfigEpochIncrements) {
    TriggerEngine eng;
    MARTe::uint32 e0 = eng.GetConfigEpoch();
    eng.SetConfig(MakeConfig(kEdgeRising, 0.0, 1.0, 20.0));
    EXPECT_EQ(e0 + 1u, eng.GetConfigEpoch());
    eng.SetConfig(MakeConfig(kEdgeFalling, 1.0, 2.0, 30.0));
    EXPECT_EQ(e0 + 2u, eng.GetConfigEpoch());
}

TEST(TriggerEngineGTest, TestMarkTriggeredOnlyFromCollecting) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 1.0, 20.0));

    /* Not collecting: no-op */
    eng.MarkTriggered();
    EXPECT_EQ(kTrigIdle, eng.GetState());
    eng.Arm();
    eng.MarkTriggered();
    EXPECT_EQ(kTrigArmed, eng.GetState());

    /* Collecting → triggered */
    eng.CheckSample(1.0, 0.0);
    eng.CheckSample(2.0, 1.0);
    ASSERT_EQ(kTrigCollecting, eng.GetState());
    eng.MarkTriggered();
    EXPECT_EQ(kTrigTriggered, eng.GetState());
}

TEST(TriggerEngineGTest, TestDisarmResetsEverything) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 1.0, 20.0));
    eng.Arm();
    eng.SetStopped(true);
    eng.CheckSample(1.0, 0.0);
    eng.CheckSample(2.0, 1.0);
    ASSERT_EQ(kTrigCollecting, eng.GetState());

    eng.Disarm();
    EXPECT_EQ(kTrigIdle, eng.GetState());
    EXPECT_FALSE(eng.GetStopped());
    float64 tt, pre, post;
    EXPECT_FALSE(eng.GetFiredWindow(tt, pre, post));
}

TEST(TriggerEngineGTest, TestRearmResetsEdgeDetection) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 1.0, 20.0));
    eng.Arm();
    eng.CheckSample(1.0, 0.0);
    eng.CheckSample(2.0, 1.0);
    eng.MarkTriggered();
    ASSERT_EQ(kTrigTriggered, eng.GetState());

    /* Re-arm: first sample only primes even if it crosses the threshold
     * relative to the value seen before the re-arm. */
    eng.Arm();
    EXPECT_EQ(kTrigArmed, eng.GetState());
    eng.CheckSample(3.0, 0.0);
    EXPECT_EQ(kTrigArmed, eng.GetState());
    eng.CheckSample(4.0, 1.0);
    EXPECT_EQ(kTrigCollecting, eng.GetState());
    float64 tt, pre, post;
    ASSERT_TRUE(eng.GetFiredWindow(tt, pre, post));
    EXPECT_DOUBLE_EQ(4.0, tt);
}

TEST(TriggerEngineGTest, TestStoppedFlag) {
    TriggerEngine eng;
    EXPECT_FALSE(eng.GetStopped());
    eng.SetStopped(true);
    EXPECT_TRUE(eng.GetStopped());
    eng.SetStopped(false);
    EXPECT_FALSE(eng.GetStopped());
}

TEST(TriggerEngineGTest, TestWindowLatchedAtFireTime) {
    TriggerEngine eng;
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 1.0, 20.0));
    eng.Arm();
    eng.CheckSample(1.0, 0.0);
    eng.CheckSample(2.0, 1.0);
    ASSERT_EQ(kTrigCollecting, eng.GetState());

    /* Config edits after the fire must not change the latched window */
    eng.SetConfig(MakeConfig(kEdgeRising, 0.5, 10.0, 50.0));
    float64 tt, pre, post;
    ASSERT_TRUE(eng.GetFiredWindow(tt, pre, post));
    EXPECT_DOUBLE_EQ(0.2, pre);
    EXPECT_DOUBLE_EQ(0.8, post);
}
