#!/usr/bin/env python3
# --------------------------------------------------------------------------
# cace_calculate.py
# --------------------------------------------------------------------------
#
# This file contains various routines that can be used from a "measure"
# block in a testbench to modify simulation results.  This can also be
# done by external scripts (e.g., octave).
#
# All result manipulations take a "results" dictionary containing a set
# of results as generated by cace_launch and returns another "results"
# dictionary with modified results and potentially modified (reduced)
# conditions.
#
# --------------------------------------------------------------------------

import os
import sys
import shutil
import json
import re
import math

from .spiceunits import spice_unit_unconvert
from .spiceunits import spice_unit_convert

# ---------------------------------------------------------------------------
# Handling of 2s complement values in calculations (e.g., "1000" is -8, not +8)
# If a value should be unsigned, then the units for the value should be one bit
# larger than represented.  e.g., if unit = "4'b" and value = "1000" then value
# is -8, but if unit = "5'b" and value = "1000" then value is +8.
# ---------------------------------------------------------------------------


def twos_complement(val, bits):
    """compute the 2's compliment of int value val"""
    if (
        val & (1 << (bits - 1))
    ) != 0:   # if sign bit is set e.g., 8bit: 128-255
        val = val - (1 << bits)        # compute negative value
    return val                         # return positive value as is


# ---------------------------------------------------------------------------
# Apply a measurement (record "measure") using vectors found in
# "varresult" and produce new vectors which overwrite the original
# ones.  Operations may reduce "varresult" vectors to a single value.
#
# NOTE:  This is all deprecated code and needs to be updated.
# ---------------------------------------------------------------------------


def cace_calculate(varresult, measure, variables):

    # 'condition' defaults to 'time';  but this only applies to transient analysis data!
    if 'condition' in measure:
        condition = measure['condition']
        if condition == 'result':
            # 'result' can either be a specified name (not recommended), or else it is
            # taken to be the variable set as the result variable.
            try:
                activevar = next(
                    item
                    for item in variables
                    if item['condition'] == condition
                )
            except StopIteration:
                try:
                    activevar = next(
                        item for item in variables if 'result' in item
                    )
                except StopIteration:
                    print(
                        'Error: measurement condition '
                        + condition
                        + ' does not exist!'
                    )
                    return 0
                else:
                    condition = activevar['condition']

    else:
        condition = 'time'

    try:
        activevar = next(
            item for item in variables if item['condition'] == condition
        )
    except:
        activeunit = ''
    else:
        if 'unit' in activevar:
            activeunit = activevar['unit']
        else:
            activeunit = ''

    try:
        activetrace = varresult[condition]
    except KeyError:
        print(
            'Measurement error:  Condition '
            + condition
            + ' does not exist in results.'
        )
        # No active trace;  cannot continue.
        return

    rsize = len(activetrace)

    if 'time' in varresult:
        timevector = varresult['time']
        try:
            timevar = next(
                item for item in variables if item['condition'] == 'time'
            )
        except:
            timeunit = 's'
        else:
            if 'unit' in timevar:
                timeunit = timevar['unit']
            else:
                timeunit = 's'
    else:
        timevector = []
        timeunit = ''

    calctype = measure['calc']
    # Diagnostic
    # print("Measure calctype = " + calctype)

    if calctype == 'result':
        # Change the 'result' marker to the indicated condition.
        for var in variables:
            if 'result' in var:
                var.pop('result')

        activevar['result'] = True

    elif calctype == 'remove':
        # Remove the indicated condition vector.
        varresult.pop(condition)

    elif calctype == 'rebase':
        # Rebase specified vector (subtract minimum value from all components)
        base = min(activetrace)
        varresult[condition] = [i - base for i in activetrace]

    elif calctype == 'abs':
        # Take absolute value of activetrace.
        varresult[condition] = [abs(i) for i in activetrace]

    elif calctype == 'negate':
        # Negate the specified vector
        varresult[condition] = [-i for i in activetrace]

    elif calctype == 'add':
        if 'value' in measure:
            v = float(measure['value'])
            varresult[condition] = [i + v for i in activetrace]
        else:
            # Add the specified vector to the result and replace the result
            varresult[condition] = [
                i + j for i, j in zip(activetrace, paramresult)
            ]

    elif calctype == 'subtract':
        if 'value' in measure:
            v = float(measure['value'])
            varresult[condition] = [i - v for i in activetrace]
        else:
            # Subtract the specified vector from the result
            varresult[condition] = [
                j - i for i, j in zip(activetrace, paramresult)
            ]

    elif calctype == 'multiply':
        if 'value' in measure:
            v = float(measure['value'])
            varresult[condition] = [i * v for i in activetrace]
        else:
            # Multiply the specified vector by the result (e.g., to get power)
            varresult[condition] = [
                j * i for i, j in zip(activetrace, paramresult)
            ]

    elif calctype == 'clip':
        if timevector == []:
            return
        # Clip specified vector to the indicated times
        if 'from' in measure:
            fromtime = float(
                spice_unit_convert([timeunit, measure['from'], 'time'])
            )
        else:
            fromtime = timevector[0]
        if 'to' in measure:
            totime = float(
                spice_unit_convert([timeunit, measure['to'], 'time'])
            )
        else:
            totime = timevector[-1]

        try:
            fromidx = next(
                i for i, j in enumerate(timevector) if j >= fromtime
            )
        except StopIteration:
            fromidx = len(timevector) - 1
        try:
            toidx = next(i for i, j in enumerate(timevector) if j >= totime)
            toidx += 1
        except StopIteration:
            toidx = len(timevector)

        for key in varresult:
            vector = varresult[key]
            varresult[key] = vector[fromidx:toidx]

        rsize = toidx - fromidx

    elif calctype == 'mean':
        if timevector == []:
            return

        # Get the mean value of all traces in the indicated range.  Results are
        # collapsed to the single mean value.
        if 'from' in measure:
            fromtime = float(
                spice_unit_convert([timeunit, measure['from'], 'time'])
            )
        else:
            fromtime = timevector[0]
        if 'to' in measure:
            totime = float(
                spice_unit_convert([timeunit, measure['to'], 'time'])
            )
        else:
            totime = timevector[-1]

        try:
            fromidx = next(
                i for i, j in enumerate(timevector) if j >= fromtime
            )
        except StopIteration:
            fromidx = len(timevector) - 1
        try:
            toidx = next(i for i, j in enumerate(timevector) if j >= totime)
            toidx += 1
        except StopIteration:
            toidx = len(timevector)

        # Correct time average requires weighting according to the size of the
        # time slice.
        tsum = timevector[toidx - 1] - timevector[fromidx]

        for key in varresult:
            vector = varresult[key]
            try:
                # Test if condition is a numeric value
                varresult[key] = vector[fromidx] + 1
            except TypeError:
                # Some conditions like 'corner' cannot be averaged, so just take the
                # first entry (may want to consider different handling)
                varresult[key] = [vector[fromidx]]
            else:
                vtot = 0.0
                for i in range(fromidx + 1, toidx):
                    # Note:  This expression can and should be optimized!
                    vtot += ((vector[i] + vector[i - 1]) / 2) * (
                        timevector[i] - timevector[i - 1]
                    )
                varresult[key] = [vtot / tsum]

        rsize = 1

    elif calctype == 'risingedge':
        if timevector == []:
            return

        # "risingedge" finds the time of a signal rising edge.
        # parameters used are:
        # 'from':   start time of search (default zero)
        # 'to':     end time of search (default end)
        # 'number': edge number (default first edge, or zero) (to be done)
        # 'cross':  measure time when signal crosses this value
        # 'keep':  determines what part of the vectors to keep
        if 'from' in measure:
            fromtime = float(
                spice_unit_convert([timeunit, measure['from'], 'time'])
            )
        else:
            fromtime = timevector[0]
        if 'to' in measure:
            totime = float(
                spice_unit_convert([timeunit, measure['to'], 'time'])
            )
        else:
            totime = timevector[-1]
        if 'cross' in measure:
            crossval = float(measure['cross'])
        else:
            crossval = (max(activetrace) + min(activetrace)) / 2
        try:
            fromidx = next(
                i for i, j in enumerate(timevector) if j >= fromtime
            )
        except StopIteration:
            fromidx = len(timevector) - 1
        try:
            toidx = next(i for i, j in enumerate(timevector) if j >= totime)
            toidx += 1
        except StopIteration:
            toidx = len(timevector)
        try:
            startidx = next(
                i
                for i, j in enumerate(activetrace[fromidx:toidx])
                if j < crossval
            )
        except StopIteration:
            startidx = 0
        startidx += fromidx
        try:
            riseidx = next(
                i
                for i, j in enumerate(activetrace[startidx:toidx])
                if j >= crossval
            )
        except StopIteration:
            riseidx = toidx - startidx - 1
        riseidx += startidx

        # If not specified, 'keep' defaults to 'instant'.
        if 'keep' in measure:
            keeptype = measure['keep']
            if keeptype == 'before':
                istart = 0
                istop = riseidx
            elif keeptype == 'after':
                istart = riseidx
                istop = len(timevector)
            else:
                istart = riseidx
                istop = riseidx + 1
        else:
            istart = riseidx
            istop = riseidx + 1

        for key in varresult:
            vector = varresult[key]
            varresult[key] = vector[istart:istop]

        rsize = istop - istart

    elif calctype == 'fallingedge':
        if timevector == []:
            return

        # 'fallingedge' finds the time of a signal rising edge.
        # parameters used are:
        # 'from':   start time of search (default zero)
        # 'to':     end time of search (default end)
        # 'number': edge number (default first edge, or zero) (to be done)
        # 'cross':  measure time when signal crosses this value
        # 'keep':  determines what part of the vectors to keep
        if 'from' in measure:
            fromtime = float(
                spice_unit_convert([timeunit, measure['from'], 'time'])
            )
        else:
            fromtime = timevector[0]
        if 'to' in measure:
            totime = float(
                spice_unit_convert([timeunit, measure['to'], 'time'])
            )
        else:
            totime = timevector[-1]
        if 'cross' in measure:
            crossval = measure['cross']
        else:
            crossval = (max(activetrace) + min(activetrace)) / 2
        try:
            fromidx = next(
                i for i, j in enumerate(timevector) if j >= fromtime
            )
        except StopIteration:
            fromidx = len(timevector) - 1
        try:
            toidx = next(i for i, j in enumerate(timevector) if j >= totime)
            toidx += 1
        except StopIteration:
            toidx = len(timevector)
        try:
            startidx = next(
                i
                for i, j in enumerate(activetrace[fromidx:toidx])
                if j > crossval
            )
        except StopIteration:
            startidx = 0
        startidx += fromidx
        try:
            fallidx = next(
                i
                for i, j in enumerate(activetrace[startidx:toidx])
                if j <= crossval
            )
        except StopIteration:
            fallidx = toidx - startidx - 1
        fallidx += startidx

        # If not specified, 'keep' defaults to 'instant'.
        if 'keep' in measure:
            keeptype = measure['keep']
            if keeptype == 'before':
                istart = 0
                istop = fallidx
            elif keeptype == 'after':
                istart = fallidx
                istop = len(timevector)
            else:
                istart = fallidx
                istop = fallidx + 1
        else:
            istart = fallidx
            istop = fallidx + 1

        for key in varresult:
            vector = varresult[key]
            varresult[key] = vector[istart:istop]

        rsize = istop - istart

    elif calctype == 'stabletime':
        if timevector == []:
            return

        # 'stabletime' finds the time at which the signal stabilizes
        # parameters used are:
        # 'from':  start time of search (default zero)
        # 'to':    end time of search (works backwards from here) (default end)
        # 'slope': measure time when signal rate of change equals this slope
        # 'keep':  determines what part of the vectors to keep
        if 'from' in measure:
            fromtime = float(
                spice_unit_convert([timeunit, measure['from'], 'time'])
            )
        else:
            fromtime = timevector[0]
        if 'to' in measure:
            totime = float(
                spice_unit_convert([timeunit, measure['to'], 'time'])
            )
        else:
            totime = timevector[-1]
        if 'limit' in measure:
            limit = float(measure['limit'])
        else:
            # Default is 5% higher or lower than final value
            limit = 0.05
        try:
            fromidx = next(
                i for i, j in enumerate(timevector) if j >= fromtime
            )
        except StopIteration:
            fromidx = len(timevector) - 1
        try:
            toidx = next(i for i, j in enumerate(timevector) if j >= totime)
        except StopIteration:
            toidx = len(timevector) - 1
        finalval = activetrace[toidx]
        toidx += 1
        highval = finalval * (1.0 + limit)
        lowval = finalval * (1.0 - limit)
        try:
            breakidx = next(
                i
                for i, j in reversed(
                    list(enumerate(activetrace[fromidx:toidx]))
                )
                if j >= highval or j <= lowval
            )
        except StopIteration:
            breakidx = 0
        breakidx += fromidx

        # If not specified, 'keep' defaults to 'instant'.
        if 'keep' in measure:
            keeptype = measure['keep']
            if keeptype == 'before':
                istart = 0
                istop = breakidx
            elif keeptype == 'after':
                istart = breakidx
                istop = len(timevector)
            else:
                istart = breakidx
                istop = breakidx + 1
        else:
            istart = breakidx
            istop = breakidx + 1

        for key in varresult:
            vector = varresult[key]
            varresult[key] = vector[istart:istop]

        rsize = istop - istart

    elif calctype == 'inside':
        if timevector == []:
            return

        # 'inside' retains only values which are inside the indicated limits
        # 'minimum':  minimum value limit to keep results
        # 'maximum':  maximum value limit to keep results
        if 'from' in measure:
            fromtime = float(
                spice_unit_convert([timeunit, measure['from'], 'time'])
            )
        else:
            fromtime = timevector[0]
        if 'to' in measure:
            totime = float(
                spice_unit_convert([timeunit, measure['to'], 'time'])
            )
        else:
            totime = timevector[-1]
        if 'minimum' in measure:
            minval = float(
                spice_unit_convert([activeunit, measure['minimum']])
            )
        else:
            minval = min(activetrace)
        if 'maximum' in measure:
            maxval = float(
                spice_unit_convert([activeunit, measure['maximum']])
            )
        else:
            maxval = max(activetrace)

        try:
            fromidx = next(
                i for i, j in enumerate(timevector) if j >= fromtime
            )
        except StopIteration:
            fromidx = len(timevector) - 1
        try:
            toidx = next(i for i, j in enumerate(timevector) if j >= totime)
            toidx += 1
        except StopIteration:
            toidx = len(timevector)
        goodidx = list(
            i
            for i, j in enumerate(activetrace[fromidx:toidx])
            if j >= minval and j <= maxval
        )
        # Diagnostic
        if goodidx == []:
            print(
                'All vector components failed bounds test.  maximum = '
                + str(max(activetrace[fromidx:toidx]))
                + '; minimum = '
                + str(min(activetrace[fromidx:toidx]))
            )

        goodidx = [i + fromidx for i in goodidx]
        for key in varresult:
            vector = varresult[key]
            varresult[key] = [vector[i] for i in goodidx]

        rsize = len(goodidx)

    return rsize


# ---------------------------------------------------------------------------
