import sys
import sqlite3
import argparse
from memUtil import *
from math import *

metrics = []
threadMetrics = []


def metric(function):
    metrics.append(function)
    return function


def threadMetric(function):
    threadMetrics.append(function)
    return function


def getThreads(connection):
    cthread = getControllerThread(connection)
    cursor = connection.cursor()
    cursor.execute("SELECT DISTINCT(Thread) FROM Transactions WHERE Thread != " + str(cthread) + " ORDER BY Thread")
    result = []
    for currentRow in cursor:
        result.append(currentRow[0])
    return result


@metric
def trace_length_in_ns(connection):
    cursor = connection.cursor()
    cursor.execute("SELECT MAX(PhaseEnd) / 1000 FROM Phases")
    result = cursor.fetchone()
    return result[0]


@metric
def command_bus_utilisation_in_percent(connection):
    cursor = connection.cursor()
    if getRowColumnCommandBus(connection):
        cursor.execute("""
            SELECT SUM(CommandLengths.Length)
            FROM Phases 
                INNER JOIN CommandLengths 
                ON Phases.PhaseName = CommandLengths.Command
            WHERE PhaseName <> 'RD' AND PhaseName <> 'RDA' AND PhaseName <> 'WR' AND PhaseName <> 'WRA'    
        """)
        rowBusUtil = cursor.fetchone()[0]
        if rowBusUtil is None:
            rowBusUtil = 0

        cursor.execute("""
            SELECT SUM(CommandLengths.Length)
            FROM Phases 
                INNER JOIN CommandLengths 
                ON Phases.PhaseName = CommandLengths.Command
            WHERE PhaseName = 'RD' OR PhaseName = 'RDA' OR PhaseName = 'WR' OR PhaseName = 'WRA'    
        """)
        columnBusUtil = cursor.fetchone()[0]
        if columnBusUtil is None:
            columnBusUtil = 0

        clk, _ = getClock(connection)
        traceEnd = getTraceEndTime(connection)
        rowBusOccupied = rowBusUtil * clk / traceEnd * 100
        columnBusOccupied = columnBusUtil * clk / traceEnd * 100
        return "row commands: {}, column commands: {}".format(rowBusOccupied, columnBusOccupied)
    else:
        cursor.execute("""
            SELECT SUM(CommandLengths.Length)
            FROM Phases 
                INNER JOIN CommandLengths 
                ON Phases.PhaseName = CommandLengths.Command
        """)
        util = cursor.fetchone()[0]
        if util is None:
            util = 0

        clk, _ = getClock(connection)
        traceEnd = getTraceEndTime(connection)
        commandBusOccupied = util * clk / traceEnd * 100
        return ": {}".format(commandBusOccupied)


@metric
def average_response_latency_in_ns(connection):
    cursor = connection.cursor()
    cursor.execute("""
        SELECT AVG(Resp.PhaseBegin -  Req.PhaseBegin) / 1000 
        FROM Phases Req, Phases Resp 
        WHERE Req.PhaseName = 'REQ' AND Resp.PhaseName='RESP' AND Req.Transact = Resp.Transact
    """)
    result = cursor.fetchone()
    return round(result[0], 1)


@metric
def average_rd_response_latency_in_ns(connection):
    cursor = connection.cursor()
    cursor.execute("""
        SELECT AVG(Resp.PhaseBegin - Req.PhaseBegin) / 1000
        FROM Phases Req, Phases Resp
        INNER JOIN Transactions
        ON Req.Transact = Transactions.ID
        WHERE Req.PhaseName = 'REQ' AND Resp.PhaseName = 'RESP' 
        AND Req.Transact = Resp.Transact AND Transactions.Command = 'R'
    """)
    result = cursor.fetchone()
    return round(result[0], 1)


@metric
def average_wr_response_latency_in_ns(connection):
    cursor = connection.cursor()
    cursor.execute("""
        SELECT AVG(Resp.PhaseBegin - Req.PhaseBegin) / 1000
        FROM Phases Req, Phases Resp
        INNER JOIN Transactions
        ON Req.Transact = Transactions.ID
        WHERE Req.PhaseName = 'REQ' AND Resp.PhaseName = 'RESP' 
        AND Req.Transact = Resp.Transact AND Transactions.Command = 'W'
    """)
    result = cursor.fetchone()
    return round(result[0], 1)


@metric
def max_response_latency_in_ns(connection):
    cursor = connection.cursor()
    cursor.execute("""
        SELECT MAX(Resp.PhaseBegin -  Req.PhaseBegin) / 1000 
        FROM Phases Req, Phases Resp
        WHERE Req.PhaseName = 'REQ' AND Resp.PhaseName = 'RESP' AND Req.Transact = Resp.Transact
    """)
    result = cursor.fetchone()
    return result[0]


@metric
def max_rd_response_latency_in_ns(connection):
    cursor = connection.cursor()
    cursor.execute("""
        SELECT MAX(Resp.PhaseBegin - Req.PhaseBegin) / 1000
        FROM Phases Req, Phases Resp
        INNER JOIN Transactions
        ON Req.Transact = Transactions.ID
        WHERE Req.PhaseName = 'REQ' AND Resp.PhaseName = 'RESP' 
        AND Req.Transact = Resp.Transact AND Transactions.Command = 'R'
    """)
    result = cursor.fetchone()
    return round(result[0], 1)


@metric
def max_wr_response_latency_in_ns(connection):
    cursor = connection.cursor()
    cursor.execute("""
        SELECT MAX(Resp.PhaseBegin - Req.PhaseBegin) / 1000
        FROM Phases Req, Phases Resp
        INNER JOIN Transactions
        ON Req.Transact = Transactions.ID
        WHERE Req.PhaseName = 'REQ' AND Resp.PhaseName = 'RESP' 
        AND Req.Transact = Resp.Transact AND Transactions.Command = 'W'
    """)
    result = cursor.fetchone()
    return round(result[0], 1)


@metric
def trans_with_max_response_latency(connection):
    cursor = connection.cursor()
    cursor.execute(""" 
        SELECT Req.Transact, MAX(Resp.PhaseBegin -  Req.PhaseBegin) / 1000 
        FROM Phases Req, Phases Resp 
        WHERE Req.PhaseName = 'REQ' AND Resp.PhaseName = 'RESP' AND Req.Transact = Resp.Transact  
    """)
    result = cursor.fetchone()
    return result[0]


@metric
def memory_active_in_clks(connection):
    cursor = connection.cursor()
    cursor.execute("SELECT SUM(DataStrobeEnd - DataStrobeBegin) FROM Phases")
    active = cursor.fetchone()[0]
    if getPseudoChannelMode(connection):
        active /= 2
    clk, _ = getClock(connection)
    return active / clk


@metric
def memory_total_in_clks(connection):
    cursor = connection.cursor()
    cursor.execute("SELECT MAX(DataStrobeEnd) FROM Phases")
    total = cursor.fetchone()
    clk, _ = getClock(connection)
    return total[0] / clk


@metric
def memory_idle_in_clks(connection):
    # This complex query identifies idle times when the DRAM is not used.
    # The code works also if schedulers are used.

    cursor = connection.cursor()

    # Create a table with transactions sorted by BeginRequest:
    # (RowNum, BeginRequest)
    query = """DROP TABLE IF EXISTS transactionsInRequestOrder;"""
    cursor.execute(query)

    query = """
        CREATE TEMPORARY TABLE transactionsInRequestOrder AS
        SELECT
            ROW_NUMBER () OVER (ORDER BY p.PhaseBegin) RowNum,
            p.PhaseBegin AS BeginRequest
        FROM 
            Phases p
        WHERE
            p.PhaseName = 'REQ';
    """
    cursor.execute(query)

    # Create a table with transactions sorted by BeginResponse:
    # (RowNum, ID, EndResponse)
    query = """DROP TABLE IF EXISTS transactionsInResponseOrder;"""
    cursor.execute(query)

    query = """
        CREATE TEMPORARY TABLE transactionsInResponseOrder AS
        SELECT
            ROW_NUMBER () OVER (ORDER BY q.PhaseBegin) RowNum,
            q.PhaseEnd AS EndResponse
        FROM 
            Phases q
        WHERE
            q.PhaseName = 'RESP';
    """
    cursor.execute(query)

    # Sum up the idle times:
    query = """
        SELECT 
            SUM(c.BeginRequest - b.EndResponse) AS idle
        FROM 
            transactionsInRequestOrder AS a, transactionsInResponseOrder AS b, transactionsInRequestOrder AS c
        WHERE
            a.RowNum = b.RowNum AND
            c.RowNum = (a.RowNum + 1) AND
            c.BeginRequest > b.EndResponse;
    """
    cursor.execute(query)
    idle = cursor.fetchone()

    cursor.execute("""SELECT MIN(PhaseBegin) FROM Phases WHERE PhaseName = 'REQ'""")
    idle_start = cursor.fetchone()
    clk, unit = getClock(connection)

    if idle[0] is None:
        return idle_start[0] / clk
    else:
        return (idle[0] + idle_start[0]) / clk


@metric
def memory_delayed_in_clks(connection):
    total = memory_total_in_clks(connection)
    active = memory_active_in_clks(connection)
    idle = memory_idle_in_clks(connection)
    return total - active - idle


@metric
def delayed_reasons(connection):
    cursor = connection.cursor()

    # Create a table with sorted data strobes:
    # (RowNum, ID, Begin, End)
    cursor.execute("DROP TABLE IF EXISTS dataStrobesInOrder;")

    cursor.execute("""
        CREATE TEMPORARY TABLE dataStrobesInOrder AS
        SELECT
            ROW_NUMBER () OVER (ORDER BY DataStrobeBegin) RowNum,
            Transact AS ID,
            DataStrobeBegin AS Begin,
            DataStrobeEnd AS End,
            Row,
            Bank,
            PhaseName AS Command
        FROM 
            Phases
        WHERE
            DataStrobeBegin <> 0 AND
            DataStrobeEnd <> 0;
    """)

    # Create a table with transactions sorted by BeginRequest:
    # (RowNum, BeginRequest)
    cursor.execute("""DROP TABLE IF EXISTS transactionsInRequestOrder;""")

    query = """
        CREATE TEMPORARY TABLE transactionsInRequestOrder AS
        SELECT
            ROW_NUMBER () OVER (ORDER BY PhaseBegin) RowNum,
            PhaseBegin AS BeginRequest
        FROM 
            Phases
        WHERE
            PhaseName = 'REQ';
    """
    cursor.execute(query)

    # Create a table with transactions sorted by BeginResponse:
    # (RowNum, ID, EndResponse)
    query = """DROP TABLE IF EXISTS transactionsInResponseOrder;"""
    cursor.execute(query)

    query = """
        CREATE TEMPORARY TABLE transactionsInResponseOrder AS
        SELECT
            ROW_NUMBER () OVER (ORDER BY PhaseBegin) RowNum,
            Transact   AS ID,
            PhaseEnd   AS EndResponse
        FROM 
            Phases
        WHERE
            PhaseName = 'RESP';
    """
    cursor.execute(query)

    # Create a table with transaction IDs that start an idle time:
    # (ID)
    query = """DROP TABLE IF EXISTS idleGaps;"""
    cursor.execute(query)

    query = """
        CREATE TEMPORARY TABLE idleGaps AS
        SELECT 
            b.ID AS ID
        FROM 
            transactionsInRequestOrder AS a, transactionsInResponseOrder AS b, transactionsInRequestOrder AS c
        WHERE
            a.RowNum = b.RowNum AND
            c.RowNum = (a.RowNum + 1) AND
            c.BeginRequest > b.EndResponse;
    """
    cursor.execute(query)

    # Create a table which features IDs that form gaps on the data bus:
    # (gapBeginID, gapEndID)
    query = """DROP TABLE IF EXISTS delayedDataBusGaps;"""
    cursor.execute(query)

    query = """
        CREATE TEMPORARY TABLE delayedDataBusGaps AS
        SELECT
            a.ID      AS gapBeginID,
            b.ID      AS gapEndID,
            a.Command AS gapBeginCommand,
            b.Command AS gapEndCommand
        FROM
            dataStrobesInOrder a,
            dataStrobesInOrder b
         WHERE 
            a.RowNum = b.RowNum - 1 AND
         	b.Begin > a.End AND
        	a.ID NOT IN (SELECT ID FROM idleGaps);
    """
    cursor.execute(query)

    # Count RW
    query = """
        SELECT
            COUNT(*)
        FROM delayedDataBusGaps
        WHERE
            (gapBeginCommand = 'RD' OR gapBeginCommand = 'RDA') AND
            (gapEndCommand = 'WR' OR gapEndCommand = 'WRA');
    """
    cursor.execute(query)
    RW = cursor.fetchone()[0]

    # Count WR
    query = """
        SELECT
            COUNT(*)
        FROM 
            delayedDataBusGaps
        WHERE
            (gapBeginCommand = 'WR' OR gapBeginCommand = 'WRA') AND
            (gapEndCommand = 'RD' OR gapEndCommand = 'RDA');
    """
    cursor.execute(query)
    WR = cursor.fetchone()[0]

    # Count RR Miss
    query = """
        SELECT COUNT(*) FROM 
        (
            SELECT
                COUNT(*)
            FROM 
                delayedDataBusGaps d, Phases p
            WHERE
                (d.gapBeginCommand = 'RD' OR d.gapBeginCommand = 'RDA') AND
                (d.gapEndCommand = 'RD' OR d.gapEndCommand = 'RDA') AND
                p.Transact = d.gapEndID AND
                p.PhaseName = 'ACT'
            GROUP BY 
                d.gapBeginID
        )	
    """
    cursor.execute(query)
    RRM = cursor.fetchone()[0]

    # Count WW Miss
    query = """
        SELECT COUNT(*) FROM 
        (
            SELECT
                COUNT(*)
            FROM 
                delayedDataBusGaps d, Phases p
            WHERE
                (d.gapBeginCommand = 'WR' OR d.gapBeginCommand = 'WRA') AND
                (d.gapEndCommand = 'WR' OR d.gapEndCommand = 'WRA') AND
                p.Transact = d.gapEndID AND
                p.PhaseName = 'ACT'
            GROUP BY 
                d.gapBeginID	
        )
    """
    cursor.execute(query)
    WWM = cursor.fetchone()[0]

    # Count All Gaps
    query = """
        SELECT
            COUNT(*)
        FROM delayedDataBusGaps;
    """
    cursor.execute(query)
    total = cursor.fetchone()[0]

    other = total - RW - WR - RRM - WWM

    RW /= total / 100.0
    WR /= total / 100.0
    RRM /= total / 100.0
    WWM /= total / 100.0
    other /= total / 100.0

    result = "RW: {}, WR: {}, RRM: {}, WWM: {}, Other: {}".format(RW, WR, RRM, WWM, other)
    return result


@metric
def memory_idle_in_percent(connection):
    total = memory_total_in_clks(connection)
    idle = memory_idle_in_clks(connection)
    return (idle / total) * 100


@metric
def memory_active_in_percent(connection):
    total = memory_total_in_clks(connection)
    active = memory_active_in_clks(connection)
    return (active / total) * 100


@metric
def memory_delayed_in_percent(connection):
    total = memory_total_in_clks(connection)
    delayed = memory_delayed_in_clks(connection)
    return (delayed / total) * 100


@metric
def memory_idle_active_delayed_check(connection):
    return memory_idle_in_percent(connection) + memory_active_in_percent(connection) \
           + memory_delayed_in_percent(connection)


@metric
def memory_utilisation_percent_without_idle(connection):
    total = memory_total_in_clks(connection)
    active = memory_active_in_clks(connection)
    idle = memory_idle_in_clks(connection)
    return (active / (total - idle)) * 100


@metric
def memory_utilisation_in_Gibps_without_idle(connection):
    # This function calculates the memory utilisation in Gibit/s considering
    # the memory_utilisation_percent_new function result.
    maxDataRate = maximum_data_rate(connection)
    memoryPercent = memory_utilisation_percent_without_idle(connection)
    return (memoryPercent / 100) * (maxDataRate / 1024)


@metric
def memory_utilisation_percent_including_idle(connection):
    cursor = connection.cursor()
    cursor.execute("""SELECT SUM(DataStrobeEnd - DataStrobeBegin) FROM Phases""")
    active = cursor.fetchone()
    cursor = connection.cursor()
    cursor.execute("""SELECT MAX(DataStrobeEnd) FROM Phases""")
    total = cursor.fetchone()
    return (active[0] / total[0]) * 100


# def refreshMissDecision(connection, calculatedMetrics):
#     cursor = connection.cursor()
#     cursor.execute("""SELECT Phases.ID, PhaseBegin, PhaseEnd, TBank FROM Phases INNER JOIN Transactions
#             ON Transactions.ID = Phases.Transact WHERE PhaseName IN ('REFAB')' """)
#     queryMinREQ = """SELECT ID, MIN(PhaseBegin) FROM (SELECT Transactions.ID, PhaseBegin FROM Transactions
#                       INNER JOIN ranges ON ranges.id = Transactions.Range INNER JOIN Phases ON Phases.Transact = Transactions.ID
#                       WHERE TThread != 0 AND TBank = :bank and PhaseName = "REQ" and ranges.begin < :begin AND ranges.end > :end)"""
#
#     queryMinRESP = """SELECT ID, MIN(PhaseBegin) FROM (SELECT Transactions.ID, PhaseBegin FROM Transactions
#                       INNER JOIN ranges ON ranges.id = Transactions.Range INNER JOIN Phases ON Phases.Transact = Transactions.ID
#                       WHERE TThread != 0 AND TBank = :bank AND PhaseName = "RESP" AND ranges.begin < :begin AND ranges.end > :end) """
#
#     missDecisions = 0
#     totalDecisions = 0
#
#     for refresh in cursor:
#         id = refresh[0]
#         begin = refresh[1]
#         end = refresh[2]
#         bank = refresh[3]
#         # print('Refresh: {0} {1} {2} {3}'.format(id,begin,end,bank))
#
#         cursorMinREQ = connection.cursor()
#         cursorMinRESP = connection.cursor()
#
#         cursorMinREQ.execute(queryMinREQ, {"bank": bank, "begin": begin, "end": end})
#         cursorMinRESP.execute(queryMinRESP, {"bank": bank, "begin": begin, "end": end})
#
#         earliestReq = cursorMinREQ.fetchone()
#         earliestResp = cursorMinRESP.fetchone()
#         if earliestReq[0] is not None:
#             totalDecisions = totalDecisions + 1
#             if earliestReq[0] != earliestResp[0]:
#                 missDecisions = missDecisions + 1
#                 # print("earliest Req: {0}| earliest Res: {1}".format(earliestReq[0], earliestResp[0]))
#
#     if totalDecisions != 0:
#         # calculatedMetrics.append(("Total Missdecisions", missDecisions))
#         calculatedMetrics.append(("Relative Missdecisions", 1.0*missDecisions/totalDecisions))
#     else:
#         calculatedMetrics.append(("Total Missdecisions", 0))
#         calculatedMetrics.append(("Relative Missdecisions", 0))


# @threadMetric # FIXME: Fix thread metrics
# def average_response_latency_in_ns(connection, thread):
#     cursor = connection.cursor()
#     query = """SELECT AVG(Resp.PhaseBegin - Req.PhaseBegin) / 1000 FROM Phases Req, Phases Resp INNER JOIN Transactions
#                ON Req.Transact = Transactions.ID WHERE Req.Transact = Resp.Transact AND Req.PhaseName = 'REQ'
#                AND Resp.PhaseName = 'RESP' AND Thread = :Thread"""
#
#     cursor.execute(query, {"Thread": thread})
#     result = cursor.fetchone()
#     return round(result[0], 1)


def addStallTime(times, begin, end):
    time = begin
    while time <= end:
        if time in times:
            times[time] = times[time] + 1
        else:
            times[time] = 1
        time = time + 1


# @threadMetric # FIXME: Fix thread metrics
# def parallelism(connection, thread):
#     cursor = connection.cursor()
#     stalltimes = {}
#     query = """SELECT Transactions.ID, MIN(PhaseBegin) / :clk, MAX(PhaseEnd) / :clk
#               FROM Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID
#               WHERE PhaseName NOT IN ('REQ','RESP') AND Thread = :Thread GROUP BY Transactions.ID """
#
#     clk, unit = getClock(connection)
#     cursor.execute(query, {"Thread": thread, "clk": clk})
#     for currentRow in cursor:
#         addStallTime(stalltimes, currentRow[1], currentRow[2])
#     para = 0
#     for time in stalltimes:
#         para = para + stalltimes[time]
#     return round(para / len(stalltimes), 2)


# @threadMetric # FIXME: Fix thread metrics
# def thread_conclusion_in_ns(connection, thread):
#     cursor = connection.cursor()
#     query = """
#         SELECT max(PhaseEnd) / 1000 FROM Phases INNER JOIN Transactions
#         ON Phases.Transact = Transactions.ID WHERE Thread = :Thread """
#     cursor.execute(query, {"Thread": thread})
#     result = cursor.fetchone()
#     return result[0]


@metric
def REQ_count(connection):
    return get_phase_occurrences(connection, 'REQ')


@metric
def RESP_count(connection):
    return get_phase_occurrences(connection, 'RESP')


# @metric
# def ACTB_count(connection):
#    return get_phase_occurrences(connection, 'ACTB')


@metric
def ACT_count(connection):
    return get_phase_occurrences(connection, 'ACT')


# @metric
# def PREB_count(connection):
#    return get_phase_occurrences(connection, 'PREB')


@metric
def PREPB_count(connection):
    return get_phase_occurrences(connection, 'PREPB') + get_phase_occurrences(connection, 'PRE')


@metric
def PRESB_count(connection):
    return get_phase_occurrences(connection, 'PRESB')


@metric
def PREAB_count(connection):
    return get_phase_occurrences(connection, 'PREAB') + get_phase_occurrences(connection, 'PREA')


@metric
def REFPB_count(connection):
    return get_phase_occurrences(connection, 'REFPB') + get_phase_occurrences(connection, 'REFB')


@metric
def REFP2B_count(connection):
    return get_phase_occurrences(connection, 'REFP2B')


@metric
def REFSB_count(connection):
    return get_phase_occurrences(connection, 'REFSB')


@metric
def REFAB_count(connection):
    return get_phase_occurrences(connection, 'REFAB') + get_phase_occurrences(connection, 'REFA')


@metric
def RFMPB_count(connection):
    return get_phase_occurrences(connection, 'RFMPB')


@metric
def RFMP2B_count(connection):
    return get_phase_occurrences(connection, 'RFMP2B')


@metric
def RFMSB_count(connection):
    return get_phase_occurrences(connection, 'RFMSB')


@metric
def RFMAB_count(connection):
    return get_phase_occurrences(connection, 'RFMAB')


@metric
def RD_count(connection):
    return get_phase_occurrences(connection, 'RD')


@metric
def RDA_count(connection):
    return get_phase_occurrences(connection, 'RDA')


@metric
def WR_count(connection):
    return get_phase_occurrences(connection, 'WR')


@metric
def WRA_count(connection):
    return get_phase_occurrences(connection, 'WRA')


@metric
def PDNA_count(connection):
    return get_phase_occurrences(connection, 'PDNA')


@metric
def PDNB_count(connection):
    return get_phase_occurrences(connection, 'PDNAB')


@metric
def PDNP_count(connection):
    return get_phase_occurrences(connection, 'PDNP')


@metric
def PDNPB_count(connection):
    return get_phase_occurrences(connection, 'PDNPB')


@metric
def SREF_count(connection):
    return get_phase_occurrences(connection, 'SREF')


@metric
def SREFB_count(connection):
    return get_phase_occurrences(connection, 'SREFB')


@metric
def number_of_accesses(connection):
    return RD_count(connection) + RDA_count(connection) + WR_count(connection) + WRA_count(connection)


@metric
def accesses_per_activate(connection):
    return round(float(number_of_accesses(connection)) / ACT_count(connection), 1)


@metric
def bank_overlap_ratio(connection):
    # Calculates how many banks are open in parallel
    clk, unit = getClock(connection)
    numberOfRanks = getNumberOfRanks(connection)
    numberOfBankGroups = getNumberOfBankGroups(connection)
    numberOfBanks = getNumberOfBanks(connection)
    per2BankOffset = getPer2BankOffset(connection)
    banksPerRank = int(numberOfBanks / numberOfRanks)
    banksPerGroup = int(numberOfBanks / numberOfBankGroups)

    cursor = connection.cursor()
    cursor.execute("SELECT TraceEnd FROM GeneralInfo")
    traceEndTMP = cursor.fetchone()
    traceEnd = int(traceEndTMP[0] / clk)
    trace = []

    cursor.execute("""
        SELECT PhaseBegin, PhaseName, Rank, BankGroup, Bank 
        FROM Phases
        WHERE (PhaseName = 'ACT' OR PhaseName = 'PREPB' OR PhaseName = 'PREP2B' OR PhaseName = 'PRESB' 
        OR PhaseName = 'PREAB' OR PhaseName = 'RDA' OR PhaseName = 'WRA')
        ORDER BY PhaseBegin
        """)

    # prevPhase = "PREAB"
    # prevTime = 0

    # clk of command, name, rank, bank group, bank
    for c in cursor:
        trace.append([(int(c[0] / clk)), c[1], c[2], c[3], c[4]])

    # Insert a pseudo precharge all to mark the end of the trace on all ranks
    for i in range(numberOfRanks):
        trace.append([traceEnd, "PREAB", i, 0, 0])

    bankCounter = [False] * numberOfBanks

    # + 1 because we have #banks + 1 active states (0 to all banks active)
    bankTime = [0.0] * (numberOfBanks + 1)
    currentTime = 0

    for t in trace:

        interval = t[0] - currentTime
        bankTime[sum(bankCounter)] += interval
        currentTime = t[0]

        if t[1] == "ACT":
            bankCounter[t[4]] = True
        elif t[1] in ["PREPB", "RDA", "WRA"]:
            bankCounter[t[4]] = False
        elif t[1] == "PREP2B":
            bankCounter[t[4]] = False
            bankCounter[t[4] + per2BankOffset] = False
        elif t[1] == "PRESB":
            for i in range(t[4], t[4] + banksPerRank, banksPerGroup):
                bankCounter[i] = False
        elif t[1] == "PREAB":
            for i in range(t[2] * banksPerRank, (t[2] + 1) * banksPerRank):
                bankCounter[i] = False
        else:
            print("ERROR")
            return 0

    for i in range(numberOfBanks + 1):
        bankTime[i] = round(bankTime[i] / traceEnd * 100, 2)

    return ",".join(format(x, "6.2f") for x in bankTime)


# @metric
# def number_of_precharges(connection):
#     cursor = connection.cursor()
#     cursor.execute("SELECT COUNT(*) FROM Phases WHERE PhaseName IN ('PRE','PREA','RDA','WRA')")
#     result = cursor.fetchone()
#     return result[0]


def time_in_PDNA_in_ns(connection):
    return get_total_time_in_phase(connection, "PDNA")


def time_in_PDNA_percent(connection):
    return (time_in_PDNA_in_ns(connection) * 1.0 / trace_length_in_ns(connection)) * 100


def time_in_PDNP_in_ns(connection):
    return get_total_time_in_phase(connection, "PDNP")


def time_in_PDNP_percent(connection):
    return (time_in_PDNP_in_ns(connection) * 1.0 / trace_length_in_ns(connection)) * 100


def time_in_SREF_in_ns(connection):
    return get_total_time_in_phase(connection, "SREF")


def time_in_SREF_percent(connection):
    return (time_in_SREF_in_ns(connection) * 1.0 / trace_length_in_ns(connection)) * 100


def time_in_PDNAB_in_ns(connection):
    return get_total_time_in_phase(connection, "PDNAB")


def time_in_PDNAB_percent(connection):
    totalTimeAllBanks = trace_length_in_ns(connection) * getNumberOfBanks(connection)
    return (time_in_PDNAB_in_ns(connection) * 1.0 / totalTimeAllBanks) * 100


def time_in_PDNPB_in_ns(connection):
    return get_total_time_in_phase(connection, "PDNPB")


def time_in_PDNPB_percent(connection):
    totalTimeAllBanks = trace_length_in_ns(connection) * getNumberOfBanks(connection)
    return (time_in_PDNPB_in_ns(connection) * 1.0 / totalTimeAllBanks) * 100


def time_in_SREFB_in_ns(connection):
    return get_total_time_in_phase(connection, "SREFB")


def time_in_SREFB_percent(connection):
    totalTimeAllBanks = trace_length_in_ns(connection) * getNumberOfBanks(connection)
    return (time_in_SREFB_in_ns(connection) * 1.0 / totalTimeAllBanks) * 100


@metric
def time_in_power_down_states_in_ns(connection):
    mcconfig = MCConfig(connection)
    # bankwiseLogic = mcconfig.getValue("BankwiseLogic")
    bankwiseLogic = "0"

    if bankwiseLogic == "0":
        totalTimeInPDNA = time_in_PDNA_in_ns(connection)
        totalTimeInPDNP = time_in_PDNP_in_ns(connection)
        totalTimeInSREF = time_in_SREF_in_ns(connection)
        totalTimePdnStates = totalTimeInPDNA + totalTimeInPDNP + totalTimeInSREF
    else:
        totalTimeInPDNAB = time_in_PDNAB_in_ns(connection)
        totalTimeInPDNPB = time_in_PDNPB_in_ns(connection)
        totalTimeInSREFB = time_in_SREFB_in_ns(connection)
        totalTimePdnStates = totalTimeInPDNAB + totalTimeInPDNPB + totalTimeInSREFB

    return totalTimePdnStates


@metric
def time_in_power_down_states_percent(connection):
    mcconfig = MCConfig(connection)
    # bankwiseLogic = mcconfig.getValue("BankwiseLogic")
    bankwiseLogic = "0"

    if bankwiseLogic == "0":
        totalTimeAllBanks = trace_length_in_ns(connection)
    else:
        totalTimeAllBanks = trace_length_in_ns(connection) * getNumberOfBanks(connection)
    return (time_in_power_down_states_in_ns(connection) * 1.0 / totalTimeAllBanks) * 100


def passRatio(connection):

    numberOfPassWins = {}
    numberOfPassLosses = {}

    for thread in getThreads(connection):
        numberOfPassWins[thread] = 0
        numberOfPassLosses[thread] = 0

    for bankNumber in range(getNumberOfBanks(connection)):
        cursor = connection.cursor()
        query = """SELECT begin,end,transactions.ID,TThread,
                      TBank from transactions inner join ranges on transactions.range = ranges.id WHERE TBank = :Bank AND TThread != 0"""
        cursor.execute(query, {"Bank": bankNumber})

        for passedRequest in cursor:
            passedBegin = passedRequest[0]
            passedEnd = passedRequest[1]
            passedId = passedRequest[2]
            passedThread = passedRequest[3]

            cursor2 = connection.cursor()
            query2 = """SELECT begin,end,transactions.ID,TThread,
                             TBank from transactions inner join ranges on transactions.range = ranges.id
                             WHERE TBank = :Bank AND :Begin<begin AND end<:End AND TThread != :Thread AND TThread != 0"""
            cursor2.execute(query2, {"Bank": bankNumber, "Thread": passedThread, "Begin": passedBegin, "End": passedEnd})

            for passingRequest in cursor2:
                numberOfPassLosses[passedThread] += 1
                numberOfPassWins[passingRequest[3]] += 1
                # print("""Transaction {0} (thread:{1} begin:{2} end:{3}) was passed by Transaction {4} (thread:{5} begin:{6} end:{7}) on bank {8}""".format(passedId,passedThread, passedBegin,passedEnd,
                #              passingRequest[2], passingRequest[3], passingRequest[0], passingRequest[1], bankNumber))

    result = []
    for thread in getThreads(connection):
        totalPassedInvolved = numberOfPassWins[thread]+numberOfPassLosses[thread]
        if(totalPassedInvolved > 0):
            passRatio = numberOfPassWins[thread]*1.0/(numberOfPassWins[thread]+numberOfPassLosses[thread])
        else:
            passRatio = 0.5
        # print("Thread {0} passed other threads {1} times and was passed {2} times. Pass ratio is {3}".format(thread, numberOfPassWins[thread], numberOfPassLosses[thread], passRatio))
        result.append(("Thread {0} pass ratio".format(thread), passRatio))
    return result


def getMetrics(pathToTrace):
    selectedMetrics = []
    connection = sqlite3.connect(pathToTrace)

    mcconfig = MCConfig(connection)
    # bankwiseLogic = mcconfig.getValue("BankwiseLogic")
    bankwiseLogic = "0"

    if bankwiseLogic == "0":
        pdnMetrics = [time_in_PDNA_in_ns, time_in_PDNA_percent, time_in_PDNP_in_ns, time_in_PDNP_percent,
                      time_in_SREF_in_ns, time_in_SREF_percent]
    else:
        pdnMetrics = [time_in_PDNAB_in_ns, time_in_PDNAB_percent, time_in_PDNPB_in_ns, time_in_PDNPB_percent,
                      time_in_SREFB_in_ns, time_in_SREFB_percent]

    for metric in pdnMetrics:
        if metric not in metrics:
            metrics.append(metric)

    if len(getThreads(connection)) >= 1:
        for metric in metrics:
            res = metric.__name__.replace("_", " ")
            selectedMetrics.append(res)

    if len(getThreads(connection)) > 1:
        for thread in getThreads(connection):
            for threadMetric in threadMetrics:
                res = "Thread {0}: {1}".format(thread, threadMetric.__name__.replace("_", " "))
                selectedMetrics.append(res)
        res = "pass ratio"
        selectedMetrics.append(res)

    connection.close()
    return selectedMetrics


def calculateMetrics(pathToTrace, selectedMetrics=None):
    if selectedMetrics is None:
        selectedMetrics = []
    calculatedMetrics = []
    connection = sqlite3.connect(pathToTrace)

    mcconfig = MCConfig(connection)
    # bankwiseLogic = mcconfig.getValue("BankwiseLogic")

    pdnMetrics = [time_in_PDNA_in_ns, time_in_PDNA_percent,
                  time_in_PDNP_in_ns, time_in_PDNP_percent,
                  time_in_SREF_in_ns, time_in_SREF_percent]

    for m in pdnMetrics:
        if m not in metrics:
            metrics.append(m)

    print("================================")
    print("Calculating metrics for {0}".format(pathToTrace))

    print("Number of threads is {0}".format(len(getThreads(connection))))

    if not selectedMetrics:
        selectedMetrics = [0] * (len(metrics) + len(getThreads(connection)) * len(threadMetrics) + 1)
        for i in range(len(selectedMetrics)):
            selectedMetrics[i] = True

    if len(getThreads(connection)) >= 1:
        for metric in metrics:
            if selectedMetrics[metrics.index(metric)]:
                mres = metric(connection)
                mname = metric.__name__.replace("_", " ")
                res = (mname, mres)

                if metric.__name__ == "bank_overlap_ratio":
                    values = mres.split(",")
                    nbanks = 0
                    for v in values:
                        name = mname + " (" + str(nbanks) + " banks active)"
                        nbanks = nbanks + 1
                        r = (name, float(v))
                        calculatedMetrics.append(r)
                elif metric.__name__ == "delayed_reasons" or metric.__name__ == "command_bus_utilisation_in_percent":
                    values = mres.split(",")
                    for v in values:
                        name = mname + " (" + v.partition(":")[0].strip() + ")"
                        value = v.partition(":")[2].strip()
                        r = (name, float(value))
                        calculatedMetrics.append(r)
                else:
                    calculatedMetrics.append(res)

                print("{0}: {1}".format(res[0], res[1]))

    if len(getThreads(connection)) > 1:
        for thread in getThreads(connection):
            for metric in threadMetrics:
                if selectedMetrics[len(metrics) + len(threadMetrics) * (thread - 1) + threadMetrics.index(metric)]:
                    mres = metric(connection, thread)
                    mname = "Thread {0} - {1}".format(thread, metric.__name__.replace("_", " "))
                    res = (mname, mres)
                    calculatedMetrics.append(res)
                print("{0}: {1}".format(res[0], res[1]))

        # if selectedMetrics[len(selectedMetrics) - 1]: # FIXME: Add pass ratio
        #     calculatedMetrics.extend(passRatio(connection))
        # refreshMissDecision(connection, calculatedMetrics)

    if len(getThreads(connection)) == 0:
        res = ("No accesses were performed for this channel, number of metrics generated", 0.0)
        calculatedMetrics.append(res)

    # refreshMissDecision(connection, calculatedMetrics)
    connection.close()
    return calculatedMetrics


def calculateMetricsFromFuncs(pathToTrace, selectedMetrics):
    calculatedMetrics = []
    connection = sqlite3.connect(pathToTrace)

    mcconfig = MCConfig(connection)

    print("================================")
    print("Calculating metrics for {0}".format(pathToTrace))

    print("Number of threads is {0}".format(len(getThreads(connection))))

    if not selectedMetrics:
        selectedMetrics = [0] * (len(metrics) + len(getThreads(connection)) * len(threadMetrics) + 1)
        for i in range(len(selectedMetrics)):
            selectedMetrics[i] = True

    for metric in selectedMetrics:
        mres = metric(connection)
        mname = metric.__name__.replace("_", " ")
        res = (mname, mres)

        if metric.__name__ == "bank_overlap_ratio":
            values = mres.split(",")
            nbanks = 0
            for v in values:
                name = mname + " (" + str(nbanks) + " banks active)"
                nbanks = nbanks + 1
                r = (name, float(v))
                calculatedMetrics.append(r)
        else:
            calculatedMetrics.append(res)

            print("{0}: {1}".format(res[0], res[1]))

    # refreshMissDecision(connection, calculatedMetrics)
    connection.close()
    return calculatedMetrics


if __name__ == "__main__":
    """
    Only non-threaded metrics are implemented for selection through command line
    """
    parser = argparse.ArgumentParser(description="Calculates metrics of a given .tdb file")

    parser.add_argument('path', type=str, help="The path to the .tdb file to be used")

    dic_metric_functions = {}
    for m in metrics:
        parser.add_argument("--" + m.__name__, action='store_true')
        dic_metric_functions[m.__name__] = m

    arg_namespace = parser.parse_args(sys.argv[1:])

    selected_metrics = []
    for k, v in arg_namespace.__dict__.items():
        if k == 'path':
            continue
        if v:
            selected_metrics.append(dic_metric_functions[k])

    if not selected_metrics:
        calculateMetrics(arg_namespace.path)
    else:
        calculateMetricsFromFuncs(arg_namespace.path, selected_metrics)
