import sys
import traceback
import sqlite3
import os
import json
from memUtil import *


class DramConfig(object):
    memoryType = ""
    scheduler = ""
    bankwiseLogic = 0
    refMode = 1
    clk = 0
    unitOfTime = ""
    dataRate = 0
    nActivateWindow = numberOfBanks = 0

    clk = 0
    tRP = 0         # precharge-time (pre -> act same bank)
    tRAS = 0        # active-time (act -> pre same bank)
    tRC = 0         # RAS-cycle-time (min time bw 2 succesive ACT to same bank)
    tCCD_S = 0      # TODO: relevant? max(bl, tCCD)
    tCCD_L = 0
    tRTP = 0        # Read to precharge
    tRRD_S = 0      # min time between 2 succesive ACT to different banks (different bank group)
    tRRD_L = 0      # min time between 2 succesive ACT to different banks (same bank group)
    tRCD = 0        # act -> read/write
    tNAW = 0        # n activate window
    tRL = 0         # read latency (read command start to data strobe)
    tWL = 0         # write latency
    tWR = 0         # write recovery (write to precharge)
    tWTR_S = 0      # write to read (different bank group)
    tWTR_L = 0      # write to read (same bank group)
    tCKESR = 0      # min time in sref
    tCKE = 0        # min time in pdna or pdnp
    tXP = 0         # min delay to row access command after pdnpx pdnax
    tXPDLL = 0      # min delay to row access command after pdnpx pdnax for dll commands
    tXS = 0        # min delay to row access command after srefx
    tXSDLL = 0     # min delay to row access command after srefx for dll commands
    tAL = 0         # additive delay (delayed execution in dram)
    tRFC = 0        # min ref->act delay
    tREFI = 0       # time between REF commands

    def readConfigFromFiles(self, connection):
        print("Parsing dram configuration")

        mcconfig = MCConfig(connection)
        memspec = MemSpec(connection)

        clkWithUnit = getClock(connection)
        self.clk = clkWithUnit[0]
        self.unitOfTime = clkWithUnit[1].lower()

        self.bankwiseLogic = 0
        self.refMode = 0
        self.scheduler = mcconfig.getValue("Scheduler")

        self.numberOfBanks = memspec.getIntValue("memarchitecturespec","nbrOfBanks")
        self.burstLength = memspec.getIntValue("memarchitecturespec","burstLength")
        self.memoryType = memspec.getValue("memoryType")
        self.dataRate = memspec.getIntValue("memarchitecturespec","dataRate")

        if (self.memoryType == "WIDEIO_SDR"):
            self.nActivateWindow = 2
            self.tRP = self.clk * memspec.getIntValue("memtimingspec","RP")
            self.tRAS = self.clk * memspec.getIntValue("memtimingspec","RAS")
            self.tRC = self.clk * memspec.getIntValue("memtimingspec","RC")
            self.tRRD_S = self.clk * memspec.getIntValue("memtimingspec","RRD")
            self.tRRD_L = self.tRRD_S
            self.tCCD_S = self.clk * memspec.getIntValue("memtimingspec","CCD")
            self.tCCD_L = self.tCCD_S
            self.tRCD = self.clk * memspec.getIntValue("memtimingspec","RCD")
            self.tNAW = self.clk * memspec.getIntValue("memtimingspec","TAW")
            self.tRL = self.clk * memspec.getIntValue("memtimingspec","RL")
            self.tWL = self.clk * memspec.getIntValue("memtimingspec","WL")
            self.tWR = self.clk * memspec.getIntValue("memtimingspec","WR")
            self.tWTR_S = self.clk * memspec.getIntValue("memtimingspec","WTR")
            self.tWTR_L = self.tWTR_S
            self.tRTP = self.clk * memspec.getIntValue("memtimingspec","RTP")
            self.tCKESR = self.clk * memspec.getIntValue("memtimingspec","CKESR")
            self.tCKE = self.clk * memspec.getIntValue("memtimingspec","CKE")
            self.tXP = self.clk * memspec.getIntValue("memtimingspec","XP")
            self.tXPDLL = self.tXP
            self.tXS = self.clk * memspec.getIntValue("memtimingspec","XS")
            self.tXSDLL = self.tXS
            self.tAL = self.clk * memspec.getIntValue("memtimingspec","AL")
            self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC")
            self.tREFI = self.clk * memspec.getIntValue("memtimingspec","REFI")

        elif (self. memoryType == "DDR4"):
            self.nActivateWindow = 4
            self.tRP = self.clk * memspec.getIntValue("memtimingspec","RP")
            self.tRAS = self.clk * memspec.getIntValue("memtimingspec","RAS")
            self.tRC = self.clk * memspec.getIntValue("memtimingspec","RC")
            self.tRTP = self.clk * memspec.getIntValue("memtimingspec","RTP")
            self.tRRD_S = self.clk * memspec.getIntValue("memtimingspec","RRD_S")
            self.tRRD_L = self.clk * memspec.getIntValue("memtimingspec","RRD_L")
            self.tCCD_S = self.clk * memspec.getIntValue("memtimingspec","CCD_S")
            self.tCCD_L = self.clk * memspec.getIntValue("memtimingspec","CCD_L")
            self.tRCD = self.clk * memspec.getIntValue("memtimingspec","RCD")
            self.tNAW = self.clk * memspec.getIntValue("memtimingspec","FAW")
            self.tRL = self.clk * memspec.getIntValue("memtimingspec","RL")
            self.tWL = self.clk * memspec.getIntValue("memtimingspec","WL")
            self.tWR = self.clk * memspec.getIntValue("memtimingspec","WR")
            self.tWTR_S = self.clk * memspec.getIntValue("memtimingspec","WTR_S")
            self.tWTR_L = self.clk * memspec.getIntValue("memtimingspec","WTR_L")
            self.tCKESR = self.clk * memspec.getIntValue("memtimingspec","CKESR")
            self.tCKE = self.clk * memspec.getIntValue("memtimingspec","CKE")
            self.tXP = self.clk * memspec.getIntValue("memtimingspec","XP")
            self.tXPDLL = self.clk * memspec.getIntValue("memtimingspec","XPDLL")
            self.tXS = self.clk * memspec.getIntValue("memtimingspec","XS")
            self.tXSDLL = self.clk * memspec.getIntValue("memtimingspec","XSDLL")
            self.tAL = self.clk * memspec.getIntValue("memtimingspec","AL")
            if (self.refMode == "4"):
                self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC4")
                self.tREFI = self.clk * (memspec.getIntValue("memtimingspec","REFI") / 4)
            elif (self.refMode == "2"):
                self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC2")
                self.tREFI = self.clk * (memspec.getIntValue("memtimingspec","REFI") / 2)
            else:
                self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC")
                self.tREFI = self.clk * memspec.getIntValue("memtimingspec","REFI")

        elif (self. memoryType == "DDR3"):
            self.nActivateWindow = 4
            self.tRP = self.clk * memspec.getIntValue("memtimingspec","RP")
            self.tRAS = self.clk * memspec.getIntValue("memtimingspec","RAS")
            self.tRC = self.clk * memspec.getIntValue("memtimingspec","RC")
            self.tRTP = self.clk * memspec.getIntValue("memtimingspec","RTP")
            self.tRRD_S = self.clk * memspec.getIntValue("memtimingspec","RRD")
            self.tRRD_L = self.clk * memspec.getIntValue("memtimingspec","RRD")
            self.tCCD_S = self.clk * memspec.getIntValue("memtimingspec","CCD")
            self.tCCD_L = self.clk * memspec.getIntValue("memtimingspec","CCD")
            self.tRCD = self.clk * memspec.getIntValue("memtimingspec","RCD")
            self.tNAW = self.clk * memspec.getIntValue("memtimingspec","FAW")
            self.tRL = self.clk * memspec.getIntValue("memtimingspec","RL")
            self.tWL = self.clk * memspec.getIntValue("memtimingspec","WL")
            self.tWR = self.clk * memspec.getIntValue("memtimingspec","WR")
            self.tWTR_S = self.clk * memspec.getIntValue("memtimingspec","WTR")
            self.tWTR_L = self.clk * memspec.getIntValue("memtimingspec","WTR")
            self.tCKESR = self.clk * memspec.getIntValue("memtimingspec","CKESR")
            self.tCKE = self.clk * memspec.getIntValue("memtimingspec","CKE")
            self.tXP = self.clk * memspec.getIntValue("memtimingspec","XP")
            self.tXPDLL = self.clk * memspec.getIntValue("memtimingspec","XPDLL")
            self.tXS = self.clk * memspec.getIntValue("memtimingspec","XS")
            self.tXSDLL = self.clk * memspec.getIntValue("memtimingspec","XSDLL")
            self.tAL = self.clk * memspec.getIntValue("memtimingspec","AL")
            self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC")
            self.tREFI = self.clk * memspec.getIntValue("memtimingspec","REFI")

        else:
            raise Exception("MemoryType not supported yet. Insert a coin into the coin machine and try again")

    def clkAlign(self, value):
        return math.ceil(1.0*value/self.clk)*self.clk

    def getWriteAccessTime(self):
        if (self.dataRate == 1):
            return self.clk*(self.burstLength - 1)
        elif (self.memoryType == "DDR4"):
            return self.clk*self.burstLength/self.dataRate
        else:  # DEFAULT
            return self.clk*self.burstLength/self.dataRate

    def getReadAccessTime(self):
        return self.burstLength/self.dataRate * dramconfig.clk

    def __init__(self):
        pass


dramconfig = DramConfig()


def calculateReadLength(burstLength):
    return dramconfig.tRL + burstLength * dramconfig.clk


def calculateWriteLength(burstLength):
    return dramconfig.tWL + burstLength * dramconfig.clk


# ----------- test utils ---------------------------------------

tests = []


def test(function):
    tests.append(function)
    return function


class TestResult(object):
    passed = True
    message = ''

    def __init__(self, passed=True, message=''):
        self.passed = passed
        self.message = message


def TestSuceeded():
    return TestResult()


def TestFailed(message):
    return TestResult(False, message)


def formatTime(time):
    return ('{0} {1}'.format(time, dramconfig.unitOfTime))

# ----------- checks ---------------------------------------


@test
def commands_are_clockaligned(connection):
    """Checks that all commands on the command bus are aligned to the system clock"""

    cursor = connection.cursor()
    query = "select ID,PhaseBegin,PhaseEnd from phases where phaseName NOT IN ('REQ','RESP') AND (phaseBegin%:clk!=0 OR phaseEnd%:clk!=0)"
    cursor.execute(query, {"clk": dramconfig.clk})

    result = cursor.fetchone()

    if (result is not None):
        return TestFailed("Command with PhaseID {0} starts at {1} and ends at. One of those times. is not aligned to system clock ({2})".format(result[0], formatTime(result[1]), formatTime(result[2]), formatTime(dramconfig.clk)))
    return TestSuceeded()


@test
def commandbus_slots_are_used_once(connection):
    """Checks that no two phases on the command bus start at the same time"""

    cursor = connection.cursor()

    if (dramconfig.bankwiseLogic == "1"):
        excludedPhases = "('REQ','RESP','PREAB')"
    else:
        excludedPhases = "('REQ','RESP','PREAB','PDNA','PDNP','SREF','REFAB')"

    query = """SELECT PhaseBegin,count FROM (SELECT phaseBegin,count(phasebegin) AS count
        FROM Phases WHERE PhaseName NOT IN """ + excludedPhases + """ AND phasebegin>0 GROUP BY phaseBegin) WHERE count>1"""

    cursor.execute(query)
    result = cursor.fetchone()
    if (result is not None):
        return TestFailed("Slot on commandbus at time {0} is used multiple times".format(formatTime(result[0])))
    return TestSuceeded()


@test
def phase_transitions_are_valid(connection):
    """Checks that all transition of two consequtive phases on the same bank are valid"""

    cursor = connection.cursor()
    validTransitions = {}

    # validTransitions tells you which phases are allowed to follow the last transaction.

    if (dramconfig.bankwiseLogic == "1"):
        validTransitions['PREPB'] = set(['ACT', 'REFPB', 'SREFB'])
        validTransitions['ACT'] = set(['RD', 'RDA', 'WR', 'WRA', 'PREPB', 'PREAB'])

        validTransitions['RD'] = set(['PREPB', 'RD', 'RDA', 'WR', 'WRA', 'PDNAB'])
        validTransitions['WR'] = set(['PREPB', 'RD', 'RDA', 'WR', 'WRA', 'PDNAB'])
        validTransitions['RDA'] = set(['ACT', 'REFPB', 'PDNPB'])
        validTransitions['WRA'] = set(['ACT', 'REFPB', 'PDNPB'])

        validTransitions['REFPB'] = set(['ACT', 'REFPB', 'PDNPB', 'SREFB'])

        validTransitions['PDNAB'] = set(['PREPB', 'RD', 'RDA', 'WR', 'WRA', 'REFPB'])
        validTransitions['PDNPB'] = set(['ACT', 'REFPB', 'SREFB'])
        validTransitions['SREFB'] = set(['ACT', 'REFPB'])
    else:
        validTransitions['PREPB'] = set(['ACT', 'PREAB', 'REFAB'])
        validTransitions['PREAB'] = set(['REFAB', 'SREF'])
        validTransitions['ACT'] = set(['RD', 'RDA', 'WR', 'WRA', 'PREAB'])

        validTransitions['RD'] = set(['PREPB', 'PREAB', 'RD', 'RDA', 'WR', 'WRA', 'PDNA'])
        validTransitions['WR'] = set(['PREPB', 'PREAB', 'RD', 'RDA', 'WR', 'WRA', 'PDNA'])
        validTransitions['RDA'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP'])
        validTransitions['WRA'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP'])

        validTransitions['REFAB'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP', 'SREF'])

        validTransitions['PDNA'] = set(['PREPB', 'PREAB', 'ACT', 'RD', 'RDA', 'WR', 'WRA', 'REFAB', 'PDNA', 'PDNP'])
        validTransitions['PDNP'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP', 'SREF'])
        validTransitions['SREF'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP'])

    if (dramconfig.bankwiseLogic == "1"):
        query = """SELECT
                        PhaseName, phases.ID
                   FROM
                        phases INNER JOIN transactions ON phases.transact=transactions.ID
                   WHERE
                        (TBank=:bank) AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
    else:
        # REFAB, PREAB, PDNA, PDNP and SREF are attributed to Bank 0 therefore this must be added to the order evaluation:
        query = """SELECT
                        PhaseName, phases.ID
                   FROM
                        phases INNER JOIN transactions ON phases.transact=transactions.ID
                   WHERE
                        ((TBank=:bank) OR PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB'))
                        AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""

    for bankNumber in range(dramconfig.numberOfBanks):
        cursor.execute(query, {"bank": bankNumber})
        lastRow = cursor.fetchone()
        for currentRow in cursor:
            currentPhase = currentRow[0]
            lastPhase = lastRow[0]

            if (currentPhase not in validTransitions[lastPhase]):
                return TestFailed("Phase {0}({1}) is not allowed to follow phase {2}({3})".format(currentRow[1], currentPhase, lastRow[1], lastPhase))
            lastRow = currentRow

    return TestSuceeded()


def timing_constraint(FirstPhase, SecondPhase):
    FirstPhaseName = FirstPhase[0]
    SecondPhaseName = SecondPhase[0]

    if ((FirstPhaseName == "PREPB" or FirstPhaseName == "PREAB") and SecondPhaseName != "PREAB"):
        return dramconfig.tRP

    elif (FirstPhaseName == "ACT"):
        return dramconfig.tRCD

    elif (FirstPhaseName == "RD"):
        if (SecondPhaseName in ["PREPB, PREAB"]):
            return dramconfig.tRTP
        elif (SecondPhaseName in ["RD, RDA"]):
            return max(dramconfig.tCCD_L, dramconfig.getReadAccessTime())
        elif (SecondPhase in ["WR", "WRA"]):
            return dramconfig.tRL + dramconfig.getReadAccessTime() - dramconfig.tWL + 2 * dramconfig.clk
        elif (SecondPhase == "PDNA"):
            return dramconfig.tRL + dramconfig.getReadAccessTime() + dramconfig.clk

    elif (FirstPhaseName == "WR"):
        if (SecondPhaseName in ["PREPB, PREAB", "PDNA"]):
            return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR
        elif (SecondPhaseName in ["RD, RDA"]):
            return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWTR_L
        elif (SecondPhaseName in ["WR, WRA"]):
            return max(dramconfig.tCCD_L, burstlength/dramconfig.dataRate)

    elif (FirstPhaseName == "RDA"):
        if (SecondPhaseName in ["ACT", "REFAB"]):
            return dramconfig.tRTP + dramconfig.tRP
        elif (SecondPhaseName == "PREAB"):
            return dramconfig.tRTP
        elif (SecondPhaseName in ["PDNA", "PDNP"]):
            return dramconfig.tRL + dramconfig.getReadAccessTime() + dramconfig.clk

    elif (FirstPhaseName == "WRA"):
        if (SecondPhaseName in ["ACT", "REFAB"]):
            return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR + dramconfig.tRP
        elif (SecondPhaseName == "PREAB"):
            return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR
        elif (SecondPhaseName in ["PDNA", "PDNP"]):
            return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR + dramconfig.clk

    elif (FirstPhaseName == "REFAB"):
        return dramconfig.tRFC

    elif (FirstPhaseName in ["PDNA", "PDNP"]):
        # print("{0}".format(FirstPhaseName))
        # print("{0}".format(formatTime(FirstPhase[3])))
        # print("{0}".format(formatTime(FirstPhase[2])))
        # print("{0}".format(formatTime(dramconfig.tXP)))
        # print("{0}".format(formatTime(dramconfig.clk)))
        return (FirstPhase[3] - FirstPhase[2]) + dramconfig.tXP - dramconfig.clk

    elif (FirstPhaseName == "SREF"):
        return (FirstPhase[3] - FirstPhase[2]) + dramconfig.tXS - dramconfig.clk

    return 0


@test
def timing_constraits_on_the_same_bank_hold(connection):
    """Checks that all transitions of two consecutive phases on the same bank meet their timing constraints"""
    cursor = connection.cursor()
    validTransitions = {}

    if (dramconfig.bankwiseLogic == "1"):
        query = """SELECT PhaseName, phases.ID, PhaseBegin, PhaseEnd FROM phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE TBank=:bank
               AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
    else:
        query = """SELECT PhaseName, phases.ID, PhaseBegin, PhaseEnd FROM phases INNER JOIN transactions ON phases.transact=transactions.ID
               WHERE ((TBank=:bank) OR PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB')) AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
    for bankNumber in range(dramconfig.numberOfBanks):
        cursor.execute(query, {"bank": bankNumber})
        lastRow = cursor.fetchone()

        for currentRow in cursor:
            constraint = timing_constraint(lastRow, currentRow)
            if (currentRow[2] - (lastRow[2] + constraint) < 0):
                return TestFailed("Phase {0}({1}) starts {2} after Start of Phase {3}({4}). Minimal time is {5}".format(currentRow[1], currentRow[0], formatTime(currentRow[2] - lastRow[2]), lastRow[1], lastRow[0], formatTime(constraint)))
            lastRow = currentRow

    return TestSuceeded()


@test
def row_buffer_is_used_correctly(connection):
    """Checks that each bank's row buffer is used correctly"""

    cursor = connection.cursor()

    if (dramconfig.bankwiseLogic == "1"):
        query = """SELECT
                        PhaseName, phases.ID
                   FROM
                        phases INNER JOIN transactions ON phases.transact=transactions.ID
                   WHERE
                        ((TBank=:bank) OR (PhaseNAME = "REFAB" AND TBank=0) OR (PhaseNAME = "PREAB" AND TBank=0))
                        AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
    else:
        # REFAB, PREAB, PDNA, PDNP and SREF are stored to bank0 for all the other banks we have also to grep this command:
        # PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB')
        query = """SELECT
                        PhaseName, phases.ID
                   FROM
                        phases INNER JOIN transactions ON phases.transact=transactions.ID
                   WHERE
                        ((TBank=:bank) OR PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB'))
                        AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""

    # phases that precharge the bank and close the rowbuffer
    prechargingPhases = set(['PREPB', 'PREAB', 'RDA', 'WRA'])

    # phases that require the bank to be in active state and the rowbuffer to be opened
    accessingPhases = set(['RD', 'RDA', 'WR', 'WRA', 'PREPB'])

    # phases that require the bank to be in precharged state and the robuffer to be closed
    idlePhases = set(['ACT', 'PDNP', 'REFAB', 'SREF'])

    for bankNumber in range(dramconfig.numberOfBanks):
        cursor.execute(query, {"bank": bankNumber})

        rowBufferIsClosed = True

        for currentRow in cursor:
            if ((currentRow[0] in accessingPhases) and (rowBufferIsClosed is True)):
                return TestFailed("Phase {0}({1}) acesses a closed rowbuffer".format(currentRow[1], currentRow[0]))

            if ((currentRow[0] in idlePhases) and (rowBufferIsClosed is False)):
                return TestFailed("Phase {0}({1}) needs a closed rowbuffer".format(currentRow[1], currentRow[0]))

            if (currentRow[0] == 'ACT'):
                rowBufferIsClosed = False

            if (currentRow[0] in prechargingPhases):
                rowBufferIsClosed = True

    return TestSuceeded()


@test
def no_commands_during_refresh(connection):
    """Checks that no command was scheduled during refresh period"""
    cursor = connection.cursor()
    if (dramconfig.bankwiseLogic == "1"):
        query = """SELECT  PhaseBegin, PhaseEnd, TBank FROM phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName = 'REFPB' """
        test_query = """SELECT PhaseName FROM phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE ((PhaseBegin >= ? and PhaseEnd <= ?) or (PhaseBegin <= ? and PhaseEnd > ?) or (PhaseBegin < ? and PhaseEnd >= ?)) and PhaseName NOT IN ('REQ','RESP','REFPB') and TBank = ?"""
    else:
        query = """SELECT  PhaseBegin, PhaseEnd FROM phases WHERE PhaseName = 'REFAB' """
        test_query = """SELECT PhaseName FROM phases WHERE ((PhaseBegin >= ? and PhaseEnd <= ?) or (PhaseBegin <= ? and PhaseEnd > ?) or (PhaseBegin < ? and PhaseEnd >= ?)) and PhaseName NOT IN ('REQ','RESP','REFAB')"""

    cursor.execute(query)
    result = cursor.fetchall()
    for row in result:
        if(dramconfig.bankwiseLogic == "1"):
            cursor.execute(test_query, (row[0], row[1], row[0], row[0], row[1], row[1], row[2]))
        else:
            cursor.execute(test_query, (row[0], row[1], row[0], row[0], row[1], row[1]))
        test = cursor.fetchone()
        if(test is not None):
            return TestFailed("A Command {0} was scheduled during a refresh period".format(test[0]))

    return TestSuceeded()


@test
def max_number_ref_burst(connection):
    """Checks that the maximum number of REFAB commands in a burst is not exceeded"""
    cursor = connection.cursor()
    query = """SELECT  PhaseBegin, PhaseEnd FROM phases WHERE PhaseName = 'REFAB' """
    prevrow = [0] * 2
    cnt = 0
    flexibleRef = getFlexibleRef(connection)
    maxRefBurst = getMaxRefBurst(connection)

    cursor.execute(query)
    result = cursor.fetchall()

    if (flexibleRef):
        maxRefBurst = maxRefBurst - 1  # Since the intersections will be used for this test, use -1 from the max

    for row in result:
        if (prevrow[1] == row[0]):
            cnt += 1
        else:
            cnt = 0  # Reset the counter every time a burst ends
        prevrow = row
        if(cnt > maxRefBurst):
            return TestFailed("Maximum number of REFAB in a burst was exceeded at {0} with {1} REFAB in sequence. Maximum allowed is {2}.".format(formatTime(row[0]), cnt, maxRefBurst))

    return TestSuceeded()


@test
@test
def max_time_without_ref(connection):
    """Checks that the maximum time allowed between REFAB/SREF commands is not exceeded"""
    cursor = connection.cursor()
    query = """SELECT  PhaseBegin, PhaseEnd FROM phases WHERE PhaseName = 'REFAB' OR PhaseName = 'SREF' """
    prevrow = [0] * 2
    flexibleRef = getFlexibleRef(connection)
    maxRefBurst = getMaxRefBurst(connection)

    cursor.execute(query)
    result = cursor.fetchall()

    if (flexibleRef):
        maxTimeWithoutRef = ((maxRefBurst + 1) * dramconfig.tREFI) + dramconfig.tRP  # Bursts are possible, so max should be the possible burst size + 1
    else:
        maxTimeWithoutRef = dramconfig.tREFI + dramconfig.tRP

    tolerance = 0.05

    maxTimeWithoutRef = maxTimeWithoutRef + dramconfig.tREFI*tolerance

    for row in result:
        timeBetweenRefs = row[0] - prevrow[1]
        if (timeBetweenRefs > maxTimeWithoutRef):
            return TestFailed("Maximum time between REF commands was exceeded at {0} with {1} between REFs. Maximum allowed is {2}.".format(formatTime(row[0]), formatTime(timeBetweenRefs), formatTime(maxTimeWithoutRef)))
        prevrow = row

    return TestSuceeded()


# ----------- activate checks ---------------------------------------
@test
def activate_to_activate_holds(connection):
    """Checks that all activates are far enough apart(JESD229 229, P. 27)"""
    cursor = connection.cursor()
    cursor.execute("SELECT phases.ID,PhaseBegin,TBankGroup FROM Phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName = 'ACT' ORDER BY PhaseBegin")
    lastRow = cursor.fetchone()

    for currentRow in cursor:
        timeBetweenActivates = currentRow[1] - lastRow[1]
        if (currentRow[2] == lastRow[2]):
            minTime = dramconfig.tRRD_L
        else:
            minTime = dramconfig.tRRD_S
        if (timeBetweenActivates < minTime):
            return TestFailed("Activates with PhaseIDs {0} and {1} are {2} apart. Minimum time between two activates is {3}".format(currentRow[0], lastRow[0], formatTime(timeBetweenActivates), formatTime(minTime)))

        lastRow = currentRow

    return TestSuceeded()


@test
def activate_to_activate_on_same_bank_holds(connection):
    """Checks that all activates on the same bank are far enough apart (JEDEC 229, P. 27)"""
    cursor = connection.cursor()
    query = "SELECT Phases.ID,PhaseBegin from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID WHERE PhaseName = 'ACT' AND TBANK = :bank ORDER BY PhaseBegin"

    for bankNumber in range(dramconfig.numberOfBanks):
        cursor.execute(query, {"bank": bankNumber})
        lastRow = cursor.fetchone()

        for currentRow in cursor:
            timeBetweenActivates = currentRow[1] - lastRow[1]
            if (timeBetweenActivates < dramconfig.tRC):
                return TestFailed("Activates with PhaseIDs {0} and {1} are {2} apart. Minimum time between two activates is {3}, since they are on the same bank({4})".format(currentRow[0], lastRow[0], formatTime(timeBetweenActivates), dramconfig.tRC))
            else:
                lastRow = currentRow

    return TestSuceeded()


@test
def n_activate_window_holds(connection):
    """Checks that the n-Activate constraint is met everywhere(JEDEC 229, P. 27)"""
    cursor = connection.cursor()
    cursor.execute("SELECT ID,PhaseBegin from Phases WHERE PhaseName = 'ACT' ORDER BY PhaseBegin")
    activateWindow = []

    for currentRow in cursor:
        activateWindow.append(currentRow[1])
        if (len(activateWindow) > dramconfig.nActivateWindow + 1):
            activateWindow.pop(0)
            if (activateWindow[dramconfig.nActivateWindow] - activateWindow[0] < dramconfig.tNAW):
                return TestFailed("Activate with PhaseID {0} and the {1} preceeding activates violate the '{1} activate window' constraint. No more than {1} activates should be in rolling time window of {2}".format(currentRow[0], dramconfig.nActivateWindow, formatTime(dramconfig.tNAW)))
    return TestSuceeded()


# ----------- read/write checks ---------------------------------------
@test
def read_to_read_holds(connection):
    """Check that the read operations do not intefere with each other on the data bus"""
    cursor = connection.cursor()
    cursor.execute("SELECT phases.ID,PhaseBegin,TBankGroup FROM Phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName IN ('RD','RDA') ORDER BY PhaseBegin")
    lastRow = cursor.fetchone()

    for currentRow in cursor:
        timeBetweenReads = currentRow[1] - lastRow[1]
        if (currentRow[2] == lastRow[2]):
            minTime = max(dramconfig.tCCD_L, dramconfig.getReadAccessTime())
        else:
            minTime = max(dramconfig.tCCD_S, dramconfig.getReadAccessTime())
        if (timeBetweenReads < minTime):
            return TestFailed("Reads with PhaseIDs {0} and {1} are {2} apart. Minimum time between two reads is {3}".format(currentRow[0], lastRow[0], formatTime(timeBetweenReads), minTime))
        lastRow = currentRow

    return TestSuceeded()


@test
def write_to_write_holds(connection):
    """Check that the write operations do not intefere with each other on the data bus"""

    cursor = connection.cursor()
    cursor.execute("SELECT phases.ID,PhaseBegin,TBankGroup FROM Phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName IN ('WR','WRA') ORDER BY PhaseBegin")
    lastRow = cursor.fetchone()

    for currentRow in cursor:
        timeBetweenWrites = currentRow[1] - lastRow[1]
        if (currentRow[2] == lastRow[2]):
            minTime = max(dramconfig.tCCD_L, dramconfig.getWriteAccessTime())
        else:
            minTime = max(dramconfig.tCCD_S, dramconfig.getWriteAccessTime())
        if (timeBetweenWrites < minTime):
            return TestFailed("Writes with PhaseIDs {0} and {1} are {2} apart. Minimum time between two writes is {3}".format(currentRow[0], lastRow[0], formatTime(timeBetweenWrites), minTime))
        lastRow = currentRow
    return TestSuceeded()


@test
def write_to_read_and_read_to_write_hold(connection):
    """Checks that read and write operation do not interfere with each other on the data bus
       and that the write-to-read constraint is met"""

    cursor = connection.cursor()
    query = """SELECT Phases.ID,PhaseBegin,PhaseName,TBankGroup from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID
               WHERE PhaseName IN ('RD','WR','RDA','WRA')  ORDER BY PhaseBegin"""

    cursor.execute(query)
    lastRow = cursor.fetchone()

    for currentRow in cursor:
        if (currentRow[2] in ["RD", "RDA"] and lastRow[2] in ["WR", "WRA"]):
            # write to read
            if (currentRow[3] == lastRow[3]):
                tWTR = dramconfig.tWTR_L
            else:
                tWTR = dramconfig.tWTR_S

            minWriteToRead = dramconfig.tWL + dramconfig.getWriteAccessTime() + tWTR
            writeToRead = currentRow[1] - lastRow[1]

            if (writeToRead < minWriteToRead):
                return TestFailed("Read {0} starts {1} after start of write {2}. Minimum time is {3}".format(currentRow[0], formatTime(writeToRead), lastRow[0], formatTime(minWriteToRead)))

        elif (currentRow[2] in ["WR", "WRA"] and lastRow[2] in ["RD", "RDA"]):
            # read to write
            minReadToWrite = dramconfig.tRL + dramconfig.getReadAccessTime() - dramconfig.tWL + dramconfig.clk * 2
            readToWrite = currentRow[1] - lastRow[1]
            if (readToWrite < minReadToWrite):
                return TestFailed("Write {0} starts {1} after start of read {2}. Minimum time is {3}".format(currentRow[0], formatTime(readToWrite), lastRow[0], formatTime(minWriteToRead)))
        lastRow = currentRow

    return TestSuceeded()


# TODO: Check if this test still is correct!
@test
def read_holds_dll_constraint_after_sref(connection):
    """Checks that all read operations are delayed long enough after the end of the self refresh powerdown state"""

    cursor = connection.cursor()
    query = """SELECT Phases.ID,PhaseBegin,PhaseName,TBankGroup from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID AND TBank = :bank
               WHERE PhaseName IN ('RD', 'RDA', 'SREF')  ORDER BY PhaseBegin"""

    for bankNumber in range(dramconfig.numberOfBanks):
        cursor.execute(query, {"bank": bankNumber})
        lastRow = cursor.fetchone()
        for currentRow in cursor:
            if (currentRow[2] in ["RD", "RDA"] and lastRow[2] == 'SREF'):
                srefEndToRead = currentRow[1] - (lastRow[1] - dramconfig.clk)
                if (srefEndToRead < dramconfig.tXSDLL):
                    return TestFailed("Read {0} starts {1} after end of sref {2}. Minimum time is {3}".format(currentRow[0], formatTime(srefEndToRead), lastRow[0], formatTime(dramconfig.tXSDLL)))
            lastRow = currentRow
    return TestSuceeded()


@test
def strict_transaction_order(connection):
    """Checks that all transactions are processed in the right order"""
    cursor = connection.cursor()
    query = """SELECT distinct t2.ID FROM Transactions t1, Transactions t2 where t2.ID > t1.ID and t2.DataStrobeBegin < t1.DataStrobeBegin and t1.DataStrobeBegin != 0 and t2.DataStrobeBegin !=0 and t1.TThread == t2.TThread;"""

    cursor.execute(query)

    transactions = ""
    for currentRow in cursor:
        transactions += str(currentRow[0]) + ","

    if (transactions != ""):
        if (dramconfig.scheduler == "FifoStrict"):
            return TestFailed("Transactions {0} is/are not in Order ".format(transactions))
        else:
            return TestResult(True, "Transactions are not in Order, however this is okay since no FifoStrict was choosen")
    return TestSuceeded()

# ----------- powerdown checks ---------------------------------------

# @test
# def sref_active_for_minimal_time(connection):
#   """Checks that after entering self refresh powerndown state, the state is active for a minimal time (JEDEC 229, P. 41)"""
#
#   cursor = connection.cursor()
#   cursor.execute("SELECT ID, PhaseEnd-clk-PhaseBegin FROM Phases, GeneralInfo WHERE PhaseName = 'SREF'")
#   for currentRow in cursor:
#       if (currentRow[1] < dramconfig.tCKESR):
#           return TestFailed("SREF with ID {0} is {1} long. Minimal time in SREF is {2}".format(currentRow[0], formatTime(currentRow[1]), dramconfig.tCKESR))
#   return TestSuceeded()

# @test
# def pdna_pdnp_active_for_minimal_time(connection):
#   """Checks that after entering active/precharged powerdown, the state is active for a minimal time (JEDEC 229, P. 41)"""
#
#   cursor = connection.cursor()
#   cursor.execute("SELECT ID,PhaseName, PhaseEnd-PhaseBegin FROM Phases, GeneralInfo WHERE PhaseName IN ('PDNA', 'PDNP') ")
#   for currentRow in cursor:
#       if (currentRow[2] < dramconfig.tCKE):
#           return TestFailed("{0} with ID {1} is {2} long. Minimal time in SREF is {3}".format(currentRow[1], currentRow[0], formatTime(currentRow[2]), dramconfig.tCKE))
#   return TestSuceeded()

# -------------------------- interface methods --------------------


def runTests(pathToTrace):
    connection = sqlite3.connect(pathToTrace)
    dramconfig.readConfigFromFiles(connection)

    testResults = []
    numberOfFailedTest = 0
    print("================================")
    print("RUNNING TEST ON {0}".format(pathToTrace))

    print("-----------------------------\n")

    for test in tests:
        testResult = test(connection)
        testName = test.__name__.replace("_", " ")
        testResults.append((testName, testResult.passed, testResult.message))
        if (testResult.passed):
            print("[passed] {0}".format(testName))
        else:
            print("[failed] {0} failed. Message: {1}".format(testName, testResult.message))
            numberOfFailedTest = numberOfFailedTest + 1

    print("\n-----------------------------")

    if (numberOfFailedTest == 0):
        print("All tests passed")
    else:
        print("{0} of {1} tests passed".format(len(tests) - numberOfFailedTest, len(tests)))

    print("================================")
    connection.close()

    return testResults

if __name__ == "__main__":
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w')
    for i in range(1, len(sys.argv)):
        runTests(sys.argv[i])
