import math


def rescale(x, x1, x2, y1, y2, mode='lin'):
    """
    Maps the value x ranging between x1 and x2 into a proportional value
    between y1 and y2.
    
    Parameters
    ----------
    x : int | float
        The value to rescale.
    x1 : int | float
        The lower limit of input range.
    x2 : int | float
        The upper limit of input range.
    y1 : int | float
        The lowest limit of output range.
    y2 : int | float
        The upper limit of output range.
    mode : string
        If mode is 'lin' then linear scaling occurs, 'cos' produces cosine
        scaling, 'exp' produces exponential scaling, and '-exp' produces 
        inverted exponential.

    Returns
    -------
    The rescaled value.
    """
    if x > x2: return y2
    if x <= x1: return y1
    # as x moves x1 to x2 mu moves 0 to 1
    mu = (x - x1) / (x2 - x1)
    if mode == 'lin':
        #return (((y2 - y1) / (x2 - x1)) * (x - x1)) + y1
        return (y1 * (1 - mu) + (y2 * mu))
    elif mode == 'cos':
        mu2 = (1 - math.cos(mu * math.pi)) / 2
        return (y1 * (1 - mu2) + y2 * mu2)
    elif mode in ['exp','-exp']:
        # # http://www.pmean.com/10/ExponentialInterpolation.html
        # if y1==0: y1=0.00001
        # return y1 * ((y2/y1) ** (mu))
        # #https://docs.fincad.com/support/developerfunc/mathref/Interpolation.htm
        # if y1==0: y1=0.00001
        # m = math.log(y2 / y1) / (x2 - x1)
        # k = y1 * math.pow(math.e, -m * x1)
        # return k * math.pow(math.e, m * x)

        # a base that yields a slope that is not too steep or shallow...
        b = 512
        if mode == 'exp':
            return y1 + ( ((y2 - y1) / b) * math.pow(b, mu) )
        b = 1/b
        return y1 + ( ((y2 - y1) / (b - 1)) * (math.pow(b, mu) - 1) )
    raise ValueError(f"mode {mode} is not 'lin', 'cos', 'exp', or '-exp'.")