"""
.. py:module:: nx
    :platform: Unix

Functions to create agent connections from
`NetworkX <https://networkx.github.io/>`_ graph structures and NetworkX graphs
from agent connections.

.. note::

    NetworkX has to be installed in order for the functions in this module to
    work. It is not installed as a default dependency.

    Use ``pip install creamas[extras]`` to install extra requirements, including NetworkX.

"""
from networkx import Graph, DiGraph

from creamas.util import sort_addrs

def connections_from_graph(env, G, edge_data=False):
    """Create connections for agents in the given environment from the given
    NetworkX graph structure.

    :param env:
        Environment where the agents live. The environment should be derived
        from :class:`~creamas.core.environment.Environment`,
        :class:`~creamas.mp.MultiEnvironment` or
        :class:`~creamas.ds.DistributedEnvironment`.

    :param G:
        NetworkX graph structure, either :class:`networkx.graph.Graph` or
        :class:`networkx.digraph.DiGraph`. The graph needs to have the same
        number of nodes as the environment has agents (excluding the managers).

    :param bool edge_data:
        If ``True``, edge data from the given graph is copied to the agents'
        :attr:`connections`.

    .. note::

        By design, manager agents are excluded from the connections and should
        not be counted towards environment's agent count.

    The created connections are stored in each agent's
    :attr:`~creamas.core.agent.CreativeAgent.connections` and the possible
    edge data is stored as key-value pairs in the connection dictionary.

    The agents are sorted by their environments' hosts and ports before each
    agent is mapped to a node in **G**. This should cause some network
    generation methods in NetworkX, e.g.
    :func:`~networkx.generators.random_graphs.connected_watts_strogatz_graph`,
    to create more connections between agents in the same environment and/or
    node when using :class:`~creamas.mp.MultiEnvironment` or
    :class:`~creamas.ds.DistributedEnvironment`.
    """
    if not issubclass(G.__class__, (Graph, DiGraph)):
        raise TypeError("Graph structure must be derived from Networkx's "
                        "Graph or DiGraph.")
    if not hasattr(env, 'get_agents'):
        raise TypeError("Parameter 'env' must have get_agents.")

    addrs = env.get_agents(addr=True)
    if len(addrs) != len(G):
        raise ValueError("The number of graph nodes and agents in the "
                         "environment (excluding the manager agent) must "
                         "match. Now got {} nodes and {} agents."
                         .format(len(G), len(addrs)))
    # Sort agent addresses to the order they were added to the environment.
    addrs = sort_addrs(addrs)
    _addrs2nodes(addrs, G)
    conn_map = _edges2conns(G, edge_data)
    env.create_connections(conn_map)

def graph_from_connections(env, directed=False):
    """Create NetworkX graph from agent connections in a given environment.

    :param env:
        Environment where the agents live. The environment must be derived from
        :class:`~creamas.core.environment.Environment`,
        :class:`~creamas.mp.MultiEnvironment` or
        :class:`~creamas.ds.DistributedEnvironment`.

    :param bool directed:
        If ``True``, creates an instance of :class:`~networkx.digraph.DiGraph`,
        otherwise creates an instance of :class:`~networkx.graph.Graph`.

    :returns: The created NetworkX graph.
    :rtype:
        :class:`~networkx.digraph.DiGraph` or :class:`~networkx.graph.Graph`

    .. note::

        If the created graph is undirected and two connected agents have
        different data stored for each other, then the data for the given edge
        is chosen randomly between the two agents.
    """
    G = DiGraph() if directed else Graph()
    conn_list = env.get_connections(data=True)
    for agent, conns in conn_list:
        G.add_node(agent)
        ebunch = []
        for nb, data in conns.items():
            ebunch.append((agent, nb, data))
        if len(ebunch) > 0:
            G.add_edges_from(ebunch)
    return G

def _addrs2nodes(addrs, G):
    """Map agent addresses to nodes in the graph.
    """
    for i, n in enumerate(G.nodes(data=True)):
        n[1]['addr'] = addrs[i]

def _edges2conns(G, edge_data=False):
    """Create a mapping from graph edges to agent connections to be created.

    :param G:
        NetworkX's Graph or DiGraph which has :attr:`addr` attribute for each
        node.

    :param bool edge_data:
        If ``True``, stores also edge data to the returned dictionary.

    :returns:
        A dictionary where keys are agent addresses and values are lists of
        addresses to which key-agent should create connections in order to
        recreate the graph structure in an agent society.

    :rtype: dict
    """
    cm = {}
    for n in G.nodes(data=True):
        if edge_data:
            cm[n[1]['addr']] = [(G.nodes[nb]['addr'], G[n[0]][nb])
                                for nb in G[n[0]]]
        else:
            cm[n[1]['addr']] = [(G.nodes[nb]['addr'], {}) for nb in G[n[0]]]
    return cm


class Song(object):

    def __init__(self, contents):
        self.contents = contents

    def sing_me_a_song(self):
        for line in self.contents:
            print(line)

#happy_bday = Song()
#print(happy_bday.contents)
#happy_bday.sing_me_a_song()

bulls_on_parade = Song(["They rally around the family",
                        "With pockets full of shells"])

bulls_on_parade.sing_me_a_song()

["Happy birthday to you",
                    "I don't want to get sued",
                    "So I'll stop right there"]


import numpy as np
import pandas as pd
import os
from keras.layers import *
from keras.models import Model
from keras.callbacks import ModelCheckpoint, EarlyStopping
from bert4keras.backend import keras, K
from bert4keras.bert import build_bert_model
from bert4keras.tokenizer import Tokenizer
from bert4keras.optimizers import Adam
from bert4keras.snippets import sequence_padding
from sklearn.model_selection import StratifiedKFold
import Levenshtein
import datetime

np.random.seed(2020)

train_df = pd.read_csv('../data/Dataset/train.csv')
valid_df = pd.read_csv('../data/Dataset/dev.csv')
test_df = pd.read_csv('../data/Dataset/test.csv')
train_ext_df = pd.read_csv('../data/External/chip2019.csv')

train_df.dropna(axis=0,inplace=True)

train_data = train_df[['query1','query2','label']].values
valid_data = valid_df[['query1','query2','label']].values
test_data = test_df[['query1','query2','label']].values
train_ext_data = train_ext_df[['question1','question2','label']].values

def build_model(mode='bert', filename='bert', lastfour=False, LR=1e-5, DR=0.2):
    path = '../data/External/'+filename+'/'
    config_path = path+'bert_config.json'
    checkpoint_path = path+'bert_model.ckpt'
    dict_path = path+'vocab.txt'

    global tokenizer
    tokenizer = Tokenizer(dict_path, do_lower_case=True)

    bert = build_bert_model(
        config_path=config_path,
        checkpoint_path=checkpoint_path,
        with_pool=True,
        model=mode,
        return_keras_model=False,
    )
    if lastfour:
        model = Model(
            inputs=bert.model.input,
            outputs=[
                bert.model.layers[-3].get_output_at(0),
                bert.model.layers[-11].get_output_at(0),
                bert.model.layers[-19].get_output_at(0),
                bert.model.layers[-27].get_output_at(0),
            ]
        )
        output = model.outputs
        output1 = Lambda(lambda x: x[:, 0], name='Pooler1')(output[0])
        output2 = Lambda(lambda x: x[:, 0], name='Pooler2')(output[1])
        output3 = Lambda(lambda x: x[:, 0], name='Pooler3')(output[2])
        output4 = Lambda(lambda x: x[:, 0], name='Pooler4')(output[3])

        output = Concatenate(axis=1)([output1, output2, output3, output4])

    else:
        output = bert.model.output

    output = Dropout(rate=DR)(output)
    output = Dense(units=2,
                   activation='softmax',
                   kernel_initializer=bert.initializer)(output)

    model = Model(bert.model.input, output)

    model.compile(
        loss='sparse_categorical_crossentropy',
        optimizer=Adam(LR),
        metrics=['accuracy'],
    )
    return model

class data_generator(object):
    def __init__(self, data, batch_size=32, random=True):
        self.data = data
        self.batch_size = batch_size
        self.random = random
        self.steps = len(self.data) // self.batch_size
        if len(self.data) % self.batch_size != 0:
            self.steps += 1

    def __len__(self):
        return self.steps

    def __iter__(self, random=False):
        idxs = list(range(len(self.data)))
        if random:
            np.random.shuffle(idxs)
        batch_token_ids, batch_segment_ids, batch_labels = [], [], []
        for i in idxs:
            text1, text2, label = self.data[i]
            token_ids, segment_ids = tokenizer.encode(text1, text2, max_length=64)
            batch_token_ids.append(token_ids)
            batch_segment_ids.append(segment_ids)
            batch_labels.append([label])
            if len(batch_token_ids) == self.batch_size or i == idxs[-1]:
                batch_token_ids = sequence_padding(batch_token_ids)
                batch_segment_ids = sequence_padding(batch_segment_ids)
                batch_labels = sequence_padding(batch_labels)
                yield [batch_token_ids, batch_segment_ids], batch_labels
                batch_token_ids, batch_segment_ids, batch_labels = [], [], []

    def forfit(self):
        while True:
            for d in self.__iter__(self.random):
                yield d

batch_size = 16

train_generator = data_generator(train_data, batch_size)
valid_generator = data_generator(valid_data, batch_size)
test_generator = data_generator(test_data, batch_size, random=False)

def f(x):
    res=1/(1+np.e**(-x))
    return res

def f_ver(x):
    res=np.log(x/(1-x))
    return res

def do_predict(fileDir):
    res = []
    fileList = os.listdir(fileDir)
    for file in fileList:
        model.load_weights(fileDir+'/'+file)
        print('predicting on '+fileDir+'/'+file)
        pred = model.predict_generator(test_generator.forfit(), steps=len(test_generator))
        res.append(pred)
    s = np.zeros_like(res[0])#(N,2)
    for i in res:
        s += f_ver(i)/len(res)
    s = f(s)
    s = s[:,1]
    return s

model = build_model(mode='bert',filename='bert',lastfour=False)
res1 = do_predict('../user_data/model_data/bert_weights')

model = build_model(mode='bert',filename='ernie',lastfour=False)
res2 = do_predict('../user_data/model_data/ernie_weights')

model = build_model(mode='bert',filename='roberta',lastfour=False)
res3 = do_predict('../user_data/model_data/roberta_weights')

res = f((2.025*f_ver(res1) + 2.025*f_ver(res2) + 1.95*f_ver(res3))/6)

alpha = 0.47

test_data[:,2] = (res>=alpha).astype('int')
train_data = np.concatenate([train_data,test_data],axis=0)

model = build_model(mode='bert', filename='bert', LR=1e-6)
early_stopping = EarlyStopping(monitor='val_loss', patience=1, verbose=1)
model.fit_generator(train_generator.forfit(),
                    steps_per_epoch=len(train_generator),
                    epochs=5,
                    validation_data=valid_generator.forfit(),
                    validation_steps=len(valid_generator),
                    callbacks=[early_stopping],
                    verbose=2,
                    )
pred_pl = model.predict_generator(test_generator.forfit(), steps=len(test_generator))
pred_pl = pred_pl[:,1]

pred = res*0.8 + pred_pl*0.2
pred = (pred>=alpha).astype('int')

for i in range(len(test_data)):
    d = test_data[i]
    texta = d[0]
    textb = d[1]
    if Levenshtein.distance(texta,textb) == 0:
        pred[i] = 1

test_df['label'] = pred
sub = test_df[['id','label']]
sub.to_csv(("../prediction_result/result_"+datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + ".csv"), header=None, index=False)


import numpy as np
import random

random.seed(10)
np.random.seed(10)

class Regression(object):
    def __init__(self, m=1, reg_param=0):
        """"
        Inputs:
          - m Polynomial degree
          - regularization parameter reg_param
        Goal:
         - Initialize the weight vector self.theta
         - Initialize the polynomial degree self.m
         - Initialize the  regularization parameter self.reg
        """
        self.m = m
        self.reg  = reg_param
        self.dim = [m+1 , 1]
        ### These two lines set the random seeds... you can ignore. #####
        random.seed(10)
        np.random.seed(10)
        #################################################################
        self.theta = np.random.standard_normal(self.dim)

    def get_poly_features(self, X):
        """
        Inputs:
        - X: A numpy array of shape (N,1) containing the data.
        Returns:
        - X_out: an augmented training data as an mth degree feature vector e.g. [1, x, x^2, ..., x^m], x \in X.
        """
        n,d = X.shape
        m = self.m
        X_out= np.zeros((n,m+1))
        if m==1:
            # ================================================================ #
            # YOUR CODE HERE:
            # IMPLEMENT THE MATRIX X_out with each entry = [1, x]
            # ================================================================ #
            X_out[:,0] = 1
            X_out[:,1] = X[:,0]
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
        else:
            # ================================================================ #
            # YOUR CODE HERE:
            # IMPLEMENT THE MATRIX X_out with each entry = [1, x, x^2,....,x^m]
            # ================================================================ #
            for i in range(m+1):
                X_out[:,i] = np.power(X[:,0], i)
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
        return X_out

    def loss_and_grad(self, X, y):
        """
        Inputs:
        - X: n x d array of training data.
        - y: n x 1 targets
        Returns:
        - loss: a real number represents the loss
        - grad: a vector of the same dimensions as self.theta containing the gradient of the loss with respect to self.theta
        """
        loss = 0.0
        grad = np.zeros_like(self.theta)
        m = self.m
        n,d = X.shape
        if m==1:
            # ================================================================ #
            # YOUR CODE HERE:
            # Calculate the loss function of the linear regression
            # and save loss function in loss.
            # Calculate the gradient and save it as grad.
            #
            # ================================================================ #
            X_poly = self.get_poly_features(X)
            y_pred = self.predict(X)
            loss = np.sum(np.square(y - y_pred)) / (2 * n) + 0.5 * self.reg * np.sum(np.square(self.theta))
            for j in range(m + 1):
                grad_j = -np.sum((y - y_pred) * X_poly[:, j]) / n
                grad[j] = grad_j + self.reg * self.theta[j]
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
        else:
            # ================================================================ #
            # YOUR CODE HERE:
            # Calculate the loss and gradient of the polynomial regression
            # with order m
            # ================================================================ #
            X_poly = self.get_poly_features(X)
            y_pred = self.predict(X)
            loss = np.sum(np.square(y - y_pred)) / (2 * n) + 0.5 * self.reg * np.sum(np.square(self.theta))
            for j in range(m + 1):
                grad_j = -np.sum((y - y_pred) * X_poly[:, j]) / n
                grad[j] = grad_j + self.reg * self.theta[j]
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
        return loss, grad

    def train_LR(self, X, y, alpha=1e-3, B=30, num_iters=10000) :
        """
        Finds the coefficients of a {d-1}^th degree polynomial
        that fits the data using least squares mini-batch gradient descent.

        Inputs:
        - X         -- numpy array of shape (n,d), features
        - y         -- numpy array of shape (n,), targets
        - alpha     -- float, learning rate
        -B          -- integer, batch size
        - num_iters -- integer, maximum number of iterations

        Returns:
        - loss_history: vector containing the loss at each training iteration.
        - self.theta: optimal weights
        """
        ### These two lines set the random seeds... you can ignore. #####
        random.seed(10)
        np.random.seed(10)
        #################################################################
        self.theta = np.random.standard_normal(self.dim)
        loss_history = []
        n,d = X.shape
        for t in np.arange(num_iters):
            X_batch = None
            y_batch = None
            # ================================================================ #
            # YOUR CODE HERE:
            # Shuffle X along the batch axis with np.random.shuffle.
            # Get the first batch_size elements X_batch from X.
            # X_batch should have shape: (B,1), y_batch should have shape: (B,).
            # ================================================================ #
            shuffled_indices = np.random.permutation(n)
            X_shuffled = X[shuffled_indices]
            y_shuffled = y[shuffled_indices]
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
            X_batch = X_shuffled[:B]
            y_batch = y_shuffled[:B]
            loss = 0.0
            grad = np.zeros_like(self.theta)
            # ================================================================ #
            # YOUR CODE HERE:
            # evaluate loss and gradient for batch data
            # save loss as loss and gradient as grad
            # update the weights self.theta
            # ================================================================ #
            loss, grad = self.loss_and_grad(X_batch, y_batch)
            self.theta = self.theta - alpha*grad
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
            loss_history.append(loss)
        return loss_history, self.theta

    def closed_form(self, X, y):
        """
        Inputs:
        - X: n x 1 array of training data.
        - y: n x 1 array of targets
        Returns:
        - self.theta: optimal weights
        """
        m = self.m
        n,d = X.shape
        loss = 0
        if m==1:
            # ================================================================ #
            # YOUR CODE HERE:
            # obtain the optimal weights from the closed form solution
            # ================================================================ #
            X_ = self.get_poly_features(X)
            ide = np.identity(m + 1)
            ide[0][0] = 0
            self.theta = np.linalg.inv(X_.T.dot(X_) + self.reg * ide).dot(X_.T).dot(y)
            #loss, _ = self.loss_and_grad(X, y)
            y_pred = self.predict(X)
            loss = np.mean(np.square(y_pred - y)) + 0.5 * self.reg * np.sum(np.square(self.theta))
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
        else:
            # ================================================================ #
            # YOUR CODE HERE:
            # Extend X with get_poly_features().
            # Predict the targets of X.
            # ================================================================ #
            X_ = self.get_poly_features(X)
            ide = np.identity(m + 1)
            ide[0][0] = 0
            self.theta = np.linalg.inv(X_.T.dot(X_) + self.reg * ide).dot(X_.T).dot(y)
            #loss, _ = self.loss_and_grad(X, y)
            y_pred = self.predict(X)
            loss = np.mean(np.square(y_pred - y)) + 0.5 * self.reg * np.sum(np.square(self.theta))
            # ================================================================ #
            # END YOUR CODE HERE
            # ================================================================ #
        return loss, self.theta

    def predict(self, X):
        """
        Inputs:
        - X: n x 1 array of training data.
        Returns:
        - y_pred: Predicted targets for the data in X. y_pred is a 1-dimensional
        array of length n.
        """
        y_pred = np.zeros(X.shape[0])
        m = self.m
        if m == 1:
            # Compute the predictions for linear regression
            for i in range(len(X)):
                y_pred[i] = (X[i] * self.theta[1]) + self.theta[0]
        else:
            # Extend X with get_poly_features()
            X_poly = self.get_poly_features(X)
            # Compute the predictions for polynomial regression
            y_pred = np.dot(X_poly, self.theta).reshape(-1)
        return y_pred


# Write your solution here
import json
def print_persons(filename: str):
    with open(filename) as my_file:
        data = my_file.read()

    courses = json.loads(data)

    for course in courses:
        hobbies = ", ".join(course['hobbies'])
        print(f"{course['name']} {course['age']} years ({hobbies})")

#OFFICIAL SOLUTION:
# import json
# def print_persons(filename: str):
#     with open(filename) as f:
#         content = f.read()
#     persons = json.loads(content)
#     for person in persons:
#         name = person['name']
#         age = person['age']
#         hobbies = ", ".join(person['hobbies'])
#         print(f"{name} {age} years ({hobbies})")


from typing import Any, Dict, List, Type, TypeVar

import attr

T = TypeVar("T", bound="WorkflowStepAddForm")

@attr.s(auto_attribs=True)
class WorkflowStepAddForm:
    """
    Attributes:
        scheme_id (str):
        step_name (str):
        enable_escalation (bool):
        escalation_action (str):
        escalation_time (str):
        step_resolved (bool):
    """

    scheme_id: str
    step_name: str
    enable_escalation: bool
    escalation_action: str
    escalation_time: str
    step_resolved: bool
    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        scheme_id = self.scheme_id
        step_name = self.step_name
        enable_escalation = self.enable_escalation
        escalation_action = self.escalation_action
        escalation_time = self.escalation_time
        step_resolved = self.step_resolved

        field_dict: Dict[str, Any] = {}
        field_dict.update(self.additional_properties)
        field_dict.update(
            {
                "schemeId": scheme_id,
                "stepName": step_name,
                "enableEscalation": enable_escalation,
                "escalationAction": escalation_action,
                "escalationTime": escalation_time,
                "stepResolved": step_resolved,
            }
        )

        return field_dict

    @classmethod
    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
        d = src_dict.copy()
        scheme_id = d.pop("schemeId")

        step_name = d.pop("stepName")

        enable_escalation = d.pop("enableEscalation")

        escalation_action = d.pop("escalationAction")

        escalation_time = d.pop("escalationTime")

        step_resolved = d.pop("stepResolved")

        workflow_step_add_form = cls(
            scheme_id=scheme_id,
            step_name=step_name,
            enable_escalation=enable_escalation,
            escalation_action=escalation_action,
            escalation_time=escalation_time,
            step_resolved=step_resolved,
        )

        workflow_step_add_form.additional_properties = d
        return workflow_step_add_form

    @property
    def additional_keys(self) -> List[str]:
        return list(self.additional_properties.keys())

    def __getitem__(self, key: str) -> Any:
        return self.additional_properties[key]

    def __setitem__(self, key: str, value: Any) -> None:
        self.additional_properties[key] = value

    def __delitem__(self, key: str) -> None:
        del self.additional_properties[key]

    def __contains__(self, key: str) -> bool:
        return key in self.additional_properties


import random, copy
import unittest, doctest

from athenaCL.libATH import unit
from athenaCL.libATH import error

OFFSET = 0.000000001 # value in seconds used for minimum spacing

#-----------------------------------------------------------------||||||||||||--
def _stepFilter(shift):
    """scalar is used to shift all values to always allow for the offset;
    the scalar value must be calculated elsewhere
    if shift is zero process
    if shfit is negative raise error
    if shift is positive do nothign
    """

    if shift <= 0:
        shift = OFFSET
    return shift

#-----------------------------------------------------------------||||||||||||--

def durToTrapezoid(tStart, propAbsSwitch, dur, rampUp, widthMax, rampDown,
                         widthMin, min=0, max=1):
    """assume dir of peal is widthOn
    will automatically convert to proportion if abs sum extends past dur
    this is an trapezoid with only one ramp; may not have complete duration time; always leads on

    >>> durToTrapezoid(0, 'absolute', 10, 3, 3, 3, .5)
    [[0, 0.0], [3, 1.0], [6, 1.0], [9, 0.0], [9.999..., 0.0]]
    """

    # will automatically sort min, max
    peak = unit.denorm(1, min, max)
    nadir = unit.denorm(0, min, max)

    if propAbsSwitch not in ['absolute', 'proportional']:
        raise error.ParameterObjectSyntaxError('incorrect switch')

    if propAbsSwitch == 'absolute':
        timeUnitDenorm = [rampUp, widthMax, rampDown, widthMin]
        if sum(timeUnitDenorm) > dur: # force proportional
            propAbsSwitch = 'proportional'

    if propAbsSwitch == 'proportional':
        timeUnit = unit.unitNormProportion([rampUp, widthMax, rampDown, widthMin])
        timeUnitDenorm = [x*dur for x in timeUnit]

    tEnd = tStart + dur
    t = tStart

    envelope = []
    envelope.append([t, nadir])

    t = t + _stepFilter(timeUnitDenorm[0]) # ramp
    envelope.append([t, peak])

    t = t + _stepFilter(timeUnitDenorm[1]) # width
    envelope.append([t, peak])

    t = t + _stepFilter(timeUnitDenorm[2]) # ramp
    envelope.append([t, nadir])

    t = tEnd - OFFSET # always measure to end
    envelope.append([t, nadir])

    return envelope

def durToAdsr(tStart, propAbsSwitch, dur, attack, decay,
                         sustain, release, susScalar, min=0, max=1):
    """create an adsr envelope
    sustain scalar is a value, w/n the unit interval, of the difference between
    min and max

    >>> durToAdsr(0, 'absolute', 10, 2, 1, 2, 2, .5)
    [[0, 0.0], [2, 1.0], [3, 0.5], [5, 0.5], [7, 0.0], [9.999..., 0.0]]

    """
    # will automatically sort min, max
    peak = unit.denorm(1, min, max)
    nadir = unit.denorm(0, min, max)
    susLevel = (peak-nadir) * unit.limit(susScalar)

    if propAbsSwitch not in ['absolute', 'proportional']:
        raise error.ParameterObjectSyntaxError('incorrect switch')

    if propAbsSwitch == 'absolute':
        timeUnitDenorm = [attack, decay, sustain, release]
        if sum(timeUnitDenorm) > dur: # force proportional
            propAbsSwitch = 'proportional'

    if propAbsSwitch == 'proportional':
        timeUnit = unit.unitNormProportion([attack, decay, sustain, release])
        timeUnitDenorm = [x*dur for x in timeUnit]

    tEnd = tStart + dur
    t = tStart

    envelope = []
    envelope.append([t, nadir])

    t = t + _stepFilter(timeUnitDenorm[0]) # attack
    envelope.append([t, peak])

    t = t + _stepFilter(timeUnitDenorm[1]) # decay
    envelope.append([t, susLevel])

    t = t + _stepFilter(timeUnitDenorm[2]) # sustain
    envelope.append([t, susLevel])

    if propAbsSwitch == 'proportional':
        t = tEnd - OFFSET # always measure to end
        envelope.append([t, nadir])
    else: # absolute
        t = t + _stepFilter(timeUnitDenorm[3]) # sustain
        envelope.append([t, nadir])
        t = tEnd - OFFSET # always measure to end
        envelope.append([t, nadir])

    return envelope

def durToUnit(tStart, dur, center, width, min=0, max=1):
    """
    this unit envelope is based on a csound model here
     iAttack     = ((1 - iSusPcent) * iSusCenterPcent) * iDur
     iRelease    = ((1 - iSusPcent) * (1-iSusCenterPcent)) * iDur
     kAmp        linen     iAmp, iAttack, iDur, iRelease

    >>> durToUnit(0, 10, .5, .5)
    [[0, 0.0], [2.5, 1.0], [7.5, 1.0], [9.999..., 0.0]]

"""
    # will automatically sort min, max
    peak = unit.denorm(1, min, max)
    nadir = unit.denorm(0, min, max)

    # normalize w/n unit interval
    center = unit.limit(center)
    width = unit.limit(width)

    # must be greater or smaller than 0/1 to avoid double points at same x
    # or, worse, the alst event not being the last
    if center >= 1:
        center = center - OFFSET
    if center <= 0:
        center = center + OFFSET
    if width >= 1:
        width = width - OFFSET
    if width <= 0:
        width = width + OFFSET

    rampUp = ((1 - width) * center) * dur
    rampDown = ((1 - width) * (1 - center)) * dur
    center = width * dur

    timeUnitDenorm = [rampUp, center, rampDown]

    tEnd = tStart + dur
    t = tStart

    envelope = []
    envelope.append([t, nadir])

    t = t + timeUnitDenorm[0] # ramp
    envelope.append([t, peak])

    t = t + timeUnitDenorm[1] # width
    envelope.append([t, peak])

    t = tEnd - OFFSET # always measure to end
    envelope.append([t, nadir])

    return envelope

#-----------------------------------------------------------------||||||||||||--

# class TestOld:
#
#     """note that there is a potential error in cases where the time interval values fall to or below zero.
#
#     zero values in some positions could cause the last point to come before the penultimate point. htis error will not be caught
#
#     the _stepFilter function attempt to catch many possible simple errors
#     """
#
#     def __init__(self):
#         pass
#
#     def testBasic(self):
#         print 'durToTrapezoid'
#         post = durToTrapezoid(0, 'proportional', 300, 10,10,10,10,0,1)
#         print post
#
#         post = durToTrapezoid(120, 'proportional', 300, 10,20000,10,10,-2,10)
#         print post
#
#
#         print 'durToAdsr'
#         post = durToAdsr(0, 'proportional', 300, 10,10,10,10,.5, 0,1)
#         print post
#
#         post = durToAdsr(120, 'proportional', 300, 10,200,100,50, .5, -2,10)
#         print post
#
#
#         print 'durToUnit'
#         post = durToUnit(0, 300, .8, .4, 0,1)
#         print post
#
#         post = durToUnit(120, 300, .01, .01, -2,10)
#         print post
#

#-----------------------------------------------------------------||||||||||||--
class Test(unittest.TestCase):

    def runTest(self):
        pass

    def testDummy(self):
        self.assertEqual(True, True)

    def testBasic(self):
        post = durToTrapezoid(0, 'proportional', 300, 10,10,10,10,0,1)
        post = durToTrapezoid(120, 'proportional', 300, 10,20000,10,10,-2,10)
        post = durToAdsr(0, 'proportional', 300, 10,10,10,10,.5, 0,1)
        post = durToAdsr(120, 'proportional', 300, 10,200,100,50, .5, -2,10)
        post = durToUnit(0, 300, .8, .4, 0,1)
        post = durToUnit(120, 300, .01, .01, -2,10)

#-----------------------------------------------------------------||||||||||||--

if __name__ == '__main__':
    from athenaCL.test import baseTest
    baseTest.main(Test)