import requests
import math

def natural_cubic_spline(xSeries, ySeries, numSeg):
    n = len(xSeries)
    alpha = [0] * n
    h = [0] * n
    l = [0] * n
    u = [0] * n
    z = [0] * n
    c = [0] * n
    b = [0] * n
    d = [0] * n
    Results = []
    k = 0
    n = n - 1

    # Step 1: Set h
    for i in range(n):
        h[i] = xSeries[i + 1] - xSeries[i]

    # Step 2: Set alpha
    for i in range(1, n):
        alpha[i] = 3 / h[i] * (ySeries[i + 1] - ySeries[i]) - 3 / h[i - 1] * (ySeries[i] - ySeries[i - 1])

    # Step 3: Set l0, u0, z0
    l[0] = 1.0
    u[0] = 0.0
    z[0] = 0.0

    # Step 4: Set li, ui, zi
    for i in range(1, n):
        l[i] = 2 * (xSeries[i + 1] - xSeries[i - 1]) - h[i - 1] * u[i - 1]
        u[i] = h[i] / l[i]
        z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i]

    # Step 5: Set ln, zn, cn
    l[n] = 1.0
    z[n] = 0.0
    c[n] = 0.0

    # Step 6: Set ci, bi, di
    for i in range(n - 1, -1, -1):
        c[i] = z[i] - u[i] * c[i + 1]
        b[i] = (ySeries[i + 1] - ySeries[i]) / h[i] - h[i] * (c[i + 1] + 2 * c[i]) / 3
        d[i] = (c[i + 1] - c[i]) / (3 * h[i])

    # Interpolation
    for i in range(n):
        x = xSeries[i]
        inc = (xSeries[i + 1] - xSeries[i]) / numSeg
        for j in range(numSeg):
            diff = x - xSeries[i]
            Sx = ySeries[i] + b[i] * diff + c[i] * (diff ** 2) + d[i] * (diff ** 3)
            devSx = b[i] + 2 * c[i] * diff + 3 * d[i] * (diff ** 2)
            Results.append([x, Sx, devSx])
            x = x + inc

    diff = xSeries[n] - xSeries[n - 1]
    Sx = ySeries[n - 1] + b[n - 1] * diff + c[n - 1] * (diff ** 2) + d[n - 1] * (diff ** 3)
    devSx = b[n - 1] + 2 * c[n - 1] * diff + 3 * d[n - 1] * (diff ** 2)
    Results.append([x, Sx, devSx])

    return Results

def clamped_cubic_spline(xSeries, ySeries, numSeg, FPO, FPN):
    n = len(xSeries)
    alpha = [0] * n
    h = [0] * n
    l = [0] * n
    u = [0] * n
    z = [0] * n
    c = [0] * n
    b = [0] * n
    d = [0] * n
    Results = []
    k = 0
    n = n - 1

    # Step 1: Set h
    for i in range(n):
        h[i] = xSeries[i + 1] - xSeries[i]

    # Step 2: Set alpha0, alphan
    alpha[0] = 3 * (ySeries[1] - ySeries[0]) / h[0] - 3 * FPO
    alpha[n] = 3 * FPN - 3 * (ySeries[n] - ySeries[n - 1]) / h[n - 1]

    # Step 3: Set alpha
    for i in range(1, n):
        alpha[i] = 3 / h[i] * (ySeries[i + 1] - ySeries[i]) - 3 / h[i - 1] * (ySeries[i] - ySeries[i - 1])

    # Step 4: Set l0, u0, z0
    l[0] = 2 * h[0]
    u[0] = 0.5
    z[0] = alpha[0] / l[0]

    # Step 5: Set li, ui, zi
    for i in range(1, n):
        l[i] = 2 * (xSeries[i + 1] - xSeries[i - 1]) - h[i - 1] * u[i - 1]
        u[i] = h[i] / l[i]
        z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i]

    # Step 6: Set ln, zn, cn
    l[n] = h[n - 1] * (2 - u[n - 1])
    z[n] = (alpha[n] - h[n - 1] * z[n - 1]) / l[n]
    c[n] = z[n]

    # Step 7: Set ci, bi, di
    for i in range(n - 1, -1, -1):
        c[i] = z[i] - u[i] * c[i + 1]
        b[i] = (ySeries[i + 1] - ySeries[i]) / h[i] - h[i] * (c[i + 1] + 2 * c[i]) / 3
        d[i] = (c[i + 1] - c[i]) / (3 * h[i])

    # Interpolation
    for i in range(n):
        x = xSeries[i]
        inc = (xSeries[i + 1] - xSeries[i]) / numSeg
        for j in range(numSeg):
            diff = x - xSeries[i]
            Sx = ySeries[i] + b[i] * diff + c[i] * (diff ** 2) + d[i] * (diff ** 3)
            devSx = b[i] + 2 * c[i] * diff + 3 * d[i] * (diff ** 2)
            Results.append([x, Sx, devSx])
            x = x + inc

    diff = xSeries[n] - xSeries[n - 1]
    Sx = ySeries[n - 1] + b[n - 1] * diff + c[n - 1] * (diff ** 2) + d[n - 1] * (diff ** 3)
    devSx = b[n - 1] + 2 * c[n - 1] * diff + 3 * d[n - 1] * (diff ** 2)
    Results.append([x, Sx, devSx])

    return Results

def monotone_cubic_spline(xSeries, ySeries, numSeg):
    n = len(xSeries)
    dxs = [0] * n
    dys = [0] * n
    ms = [0] * n
    c1s = [0] * n
    c2s = [0] * n
    c3s = [0] * n
    Results = []
    k = 0
    n = n - 1

    # Get consecutive differences and slopes
    for i in range(n):
        dxs[i] = xSeries[i + 1] - xSeries[i]
        dys[i] = ySeries[i + 1] - ySeries[i]
        ms[i] = dys[i] / dxs[i]

    # Get degree-1 coefficients
    c1s[0] = ms[0]
    for i in range(n):
        m = ms[i]
        mNext = ms[i + 1]
        if m * mNext <= 0:
            c1s[i + 1] = 0
        else:
            dx = dxs[i]
            dxNext = dxs[i + 1]
            common = dx + dxNext
            c1s[i + 1] = 3 * common / ((common + dxNext) / m + (common + dx) / mNext)

    c1s[n] = ms[n - 1]

    # Get degree-2 and degree-3 coefficients
    for i in range(n):
        c1 = c1s[i]
        m_ = ms[i]
        invDx = 1 / dxs[i]
        common_ = c1 + c1s[i + 1] - m_ - m_
        c2s[i] = (m_ - c1 - common_) * invDx
        c3s[i] = common_ * invDx * invDx

    # Interpolation
    for i in range(n):
        x = xSeries[i]
        inc = (xSeries[i + 1] - xSeries[i]) / numSeg
        for j in range(numSeg):
            diff = x - xSeries[i]
            Sx = ySeries[i] + c1s[i] * diff + c2s[i] * (diff ** 2) + c3s[i] * (diff ** 3)
            devSx = c1s[i] + 2 * c2s[i] * diff + 3 * c3s[i] * (diff ** 2)
            Results.append([x, Sx, devSx])
            x = x + inc
            k = k + 1

    diff = xSeries[n] - xSeries[n - 1]
    Sx = ySeries[n - 1] + c1s[n - 1] * diff + c2s[n - 1] * (diff ** 2) + c3s[n - 1] * (diff ** 3)
    devSx = c1s[n - 1] + 2 * c2s[n - 1] * diff + 3 * c3s[n - 1] * (diff ** 2)
    Results.append([x, Sx, devSx])

    return Results

def MidasAPI(method, command, body=None):
    base_url = "base_url_here"
    mapi_key = "your_api_key_here"

    url = base_url + command
    headers = {
        "Content-Type": "application/json",
        "MAPI-Key": mapi_key
    }

    if method == "POST":
        response = requests.post(url=url, headers=headers, json=body)
    elif method == "PUT":
        response = requests.put(url=url, headers=headers, json=body)
    elif method == "GET":
        response = requests.get(url=url, headers=headers)
    elif method == "DELETE":
        response = requests.delete(url=url, headers=headers)

    print(method, command, response.status_code)
    return response.json()

# Input Data
# 1. Get node ID
node_ID = list(range(1, 12))

# 2. Get node coordinate
node_coordinate = MidasAPI("GET", "/db/node")

node_x = [node_coordinate["NODE"][str(value)]["X"] for value in node_ID]
node_y = [node_coordinate["NODE"][str(value)]["Y"] for value in node_ID]
num_seg = 10
start_slop = 1.0
end_slop = 1.0

# 3. Get node tangent
def get_valid_input(min_value, max_value):
    while True:
        try:
            user_input = int(input(f"Please choose the method({min_value} to {max_value}) : "))
            if min_value <= user_input <= max_value:
                return user_input
            else:
                print(f"The input should be between {min_value} and {max_value}. Please try again.")
        except ValueError:
            print("Please enter a valid integer.")
min_value = 1
max_value = 3
user_integer = get_valid_input(min_value, max_value)

if user_integer == 1 :
    tagential = natural_cubic_spline(node_x, node_y, num_seg)
elif user_integer == 2:
    tagential = clamped_cubic_spline(node_x, node_y, num_seg, start_slop, end_slop)
elif user_integer == 3:
    tagential = monotone_cubic_spline(node_x, node_y, num_seg)

spline_x = [result[0] for result in tagential]
spline_y = [result[1] for result in tagential]
spline_t = [result[2] for result in tagential]

# 4. Get tangent existing point
point_t = [value for index, value in enumerate(spline_t) if index % num_seg == 0]

# 5. Create Json format
body = {"Assign":{}}
for index, value in enumerate(node_ID):
    body["Assign"][str(value)] = {"iMETHOD":1, "ANGLE_Z": math.degrees(math.atan(point_t[index]))}

# 6. Update tangent
MidasAPI("PUT", "/db/skew", body=body)
