# SPDX-License-Identifier: Apache-2.0
# Copyright (C) 2020 ifm electronic gmbh
#
# THE PROGRAM IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
#

import glob
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import datetime
import platform
import os
import pytest

def parse_log(strlog):
    res = []
    for l in strlog.split("\n"):
        l = l.strip()
        if l == "":
            continue
        try:
            t = datetime.datetime.strptime(l[:19], '%Y-%m-%d %H:%M:%S')
            ms = int(l[20:23])
            t = t + datetime.timedelta(microseconds=ms*1000)
            l = l[24:]
            level = l[:l.find(" ")]
            l = l[l.find(" ")+1:]
            module = l[:l.find(":")]
            l = l[l.find(":")+1:]
            msg = l.strip()
            res.append((t,level,module,msg))
        except:
            pass
    return res

@pytest.mark.parametrize("pyver", ["default", "3.9", "3.10", "3.11", "3.12", "3.13"])
def test_BasicWorkflow(pyver):
    with tempfile.TemporaryDirectory() as d:
        project_dir = Path(__file__).parent.parent.parent.parent

        if pyver == "default":
            python_exe = [sys.executable]
        else:
            env = os.environ.copy()
            env["DO_NOT_REMOVE_BINARIES"] = "1"

            subprocess.check_call([sys.executable, "-m", "uv", "venv", "-p", pyver, str(Path(d) / "venv")])
            subprocess.check_call([sys.executable, "-m", "uv", "pip", "install", "-e", str(project_dir)], env=env)
            if platform.system().lower() == "windows":
                python_exe = [Path(d) / "venv" / "Scripts" / "python.exe"]
            else:
                python_exe = [Path(d) / "venv" / "bin" / "python"]

        shutil.copy(project_dir / "nexxT" / "tests" / "integration" / "basicworkflow_script.py", d)
        shutil.copytree(project_dir / "nexxT" / "tests" / "binary", Path(d) / "binary")
        shutil.copy(project_dir / "nexxT" / "tests" / "interface" / "SimpleStaticFilter.py", d)

        h5files = []
        for stage in range(3):

            if stage == 0:
                args = ["-e", "stage=0", "-s", "basicworkflow_script.py"]
            elif stage == 1:
                args = ["-e", "stage=1", "-s", "basicworkflow_script.py", "-a", "myApp", "basicworkflow.json"]
            elif stage == 2:
                args = ["-e", "stage=2", "-s", "basicworkflow_script.py", "-a", "pbApp", "basicworkflow.json"]

            try:
                p = subprocess.run(
                    [sys.executable, "-m", "nexxT.core.AppConsole", "--gui", "false"] + args,
                    cwd=d, capture_output=True, timeout=30., encoding="utf-8")
                timeout = False
            except subprocess.TimeoutExpired as e:
                p = e
                timeout = True

            print("STDOUT", p.stdout)
            print("STDERR", p.stderr)
            assert not timeout
            p.check_returncode()
            assert p.stdout.strip() == ""
            logs = parse_log(p.stderr)
            if stage < 2:
                runs = []
                transmitsBeforeActivated = [] # it might happen, that the initial "app activated" log happens after the first transmit
                receivesBeforeActivated = []
                for l in logs:
                    if l[-1] == "app activated":
                        runs.append(dict(transmit=transmitsBeforeActivated,receive=receivesBeforeActivated))
                        transmitsBeforeActivated = []
                        receivesBeforeActivated = []
                    if l[-1].startswith("Transmitting Sample") or l[-1].startswith("transmit:"):
                        if len(runs) > 0:
                            runs[-1]["transmit"].append(l[0])
                        else:
                            transmitsBeforeActivated.append(l[0])
                    if l[-1].startswith("received:"):
                        if len(runs) > 0:
                            runs[-1]["receive"].append(l[0])
                        else:
                            receivesBeforeActivated.append(l[0])
                assert len(runs) == 3
                assert len(transmitsBeforeActivated) == 0
                assert len(receivesBeforeActivated) == 0
                for r in runs:
                    nt = len(r["transmit"])
                    nr = len(r["receive"])
                    assert nt == nr or nt == nr+1
                    dtt = [(r["transmit"][i] - r["transmit"][i-1]).total_seconds() for i in range(1, nt)]
                    dtr = [(r["receive"][i] - r["receive"][i-1]).total_seconds() for i in range(1, nr)]
                    # these assertion sometimes cause tests to fail, especially for wgeb build servers are busy with other tasks
                    # let's be more tolerant and check that more than 95% of the conditions are satisfied
                    assert sum([0.07 < dt < 0.13 for dt in dtt]) >= len(dtt)*95//100
                    assert sum([0.07 < dt < 0.13 for dt in dtr]) >= len(dtr)*95//100
                recs = glob.glob(d + "/*.h5")
                assert len(recs) == len(h5files) + 1
                h5files = recs
            else:
                allFrames = []
                sample1Cnt = 0
                sample2Cnt = 0
                lastSample1 = None
                lastSample2 = None
                mode = "playing"
                for l in logs:
                    if mode == "playing":
                        if l[-1] == "played":
                            mode = "seekBegin"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            allFrames.append(l)
                            idx = int(l[-1][len("(flt2) received: Sample "):])
                            if "(flt1)" in l[-1]:
                                sample1Cnt += 1
                                assert lastSample1 is None or idx == lastSample1 + 1
                                lastSample1 = idx
                            if "(flt2)" in l[-1]:
                                sample2Cnt += 1
                                assert lastSample2 is None or idx == lastSample2 + 1
                                lastSample2 = idx
                    elif mode == "seekBegin":
                        if l[-1] == "seekBeginning":
                            nsteps = 0
                            idx = 1
                            mode = "stepFwd[None]"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert l[-1] == allFrames[0][-1]
                    elif mode == "stepFwd[None]":
                        if l[-1] == "stepForward[None]":
                            idx += 1
                            nsteps += 1
                            if idx > 10:
                                nsteps = 0
                                expectPause = False
                                mode = "stepFwd[stream1]"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert l[-1] == allFrames[idx][-1]
                    elif mode == "stepFwd[stream1]":
                        if l[-1] == "stepForward[stream1]":
                            assert expectPause
                            expectPause = False
                            nsteps += 1
                            if nsteps >= 10:
                                nsteps = 0
                                expectPause = False
                                mode = "stepFwd[stream2]"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert not expectPause
                            assert l[-1] == allFrames[idx][-1]
                            idx += 1
                            if "(flt1)" in l[-1]:
                                expectPause = True
                    elif mode == "stepFwd[stream2]":
                        if l[-1] == "stepForward[stream2]":
                            assert expectPause or idx >= len(allFrames)
                            expectPause = False
                            nsteps += 1
                            if nsteps >= 2:
                                nsteps = 0
                                expectPause = False
                                mode = "seekTime"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert not expectPause
                            assert l[-1] == allFrames[idx][-1]
                            idx += 1
                            if "(flt2)" in l[-1]:
                                expectPause = True
                    elif mode == "seekTime":
                        if l[-1] == "seekTime":
                            assert expectPause
                            mode = "seekEnd"
                            expectPause = False
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert not expectPause
                            idx = [f[-1] for f in allFrames].index(l[-1])
                            assert idx >= len(allFrames)/3 and idx <= len(allFrames)*2/3
                            expectPause = True
                    elif mode == "seekEnd":
                        if l[-1] == "seekEnd":
                            assert expectPause
                            mode = "stepBwd[None]"
                            expectPause = False
                            nsteps = 0
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert not expectPause
                            assert l[-1] == allFrames[-1][-1]
                            idx = len(allFrames)-1
                            expectPause = True
                    elif mode == "stepBwd[None]":
                        if l[-1] == "stepBackward[None]":
                            assert expectPause
                            expectPause = False
                            nsteps += 1
                            if nsteps == 10:
                                mode = "seekTimeBegin"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            idx -= 1
                            assert l[-1] == allFrames[idx][-1]
                            expectPause = True
                    elif mode == "seekTimeBegin":
                        if l[-1] == "seekTimeBegin":
                            assert expectPause
                            expectPause = False
                            mode = "seekTimeEnd"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert l[-1] == allFrames[0][-1]
                            expectPause = True
                    elif mode == "seekTimeEnd":
                        if l[-1] == "seekTimeEnd":
                            assert expectPause
                            expectPause = False
                            mode = "done"
                        if "(flt1)" in l[-1] or "(flt2)" in l[-1]:
                            assert l[-1] == allFrames[-1][-1]
                            expectPause = True
                    elif mode == "done":
                        assert not "(flt1)" in l[-1] and not "(flt2)" in l[-1]
                    else:
                        assert False, "Unknown mode %s" % mode



if __name__ == "__main__":
    test_BasicWorkflow()
