# -*- coding: utf-8 -*-
"""
Created on Tue Aug 31 09:59:05 2021

@author: Julien

https://en.wikipedia.org/wiki/L%C3%A9vy_C_curve
"""

#  _____                            _       
# |_   _|                          | |      
#   | |  _ __ ___  _ __   ___  _ __| |_ ___ 
#   | | | '_ ` _ \| '_ \ / _ \| '__| __/ __|
#  _| |_| | | | | | |_) | (_) | |  | |_\__ \
# |_____|_| |_| |_| .__/ \___/|_|   \__|___/
#                 | |                       
#                 |_|

import cmath
import numpy as np

#   _____                _              _       
#  / ____|              | |            | |      
# | |     ___  _ __  ___| |_ __ _ _ __ | |_ ___ 
# | |    / _ \| '_ \/ __| __/ _` | '_ \| __/ __|
# | |___| (_) | | | \__ \ || (_| | | | | |_\__ \
#  \_____\___/|_| |_|___/\__\__,_|_| |_|\__|___/

X_LIMITS = (-0.6, 1.6)
Y_LIMITS = (-0.4, 1.1)

#  ______                _   _                 
# |  ____|              | | (_)                
# | |__ _   _ _ __   ___| |_ _  ___  _ __  ___ 
# |  __| | | | '_ \ / __| __| |/ _ \| '_ \/ __|
# | |  | |_| | | | | (__| |_| | (_) | | | \__ \
# |_|   \__,_|_| |_|\___|\__|_|\___/|_| |_|___/

def compute_new(z1, z2, up):
    '''
    uses complex numbers to compute the next points to add for each edge.

    Parameters
    ----------
    z1 : complex
        first vertice.
    z2 : complex
        second vertice.
    up : boolean
        wether or not add the point in a clockwise or counterclockwise fashion.

    Returns
    -------
    complex
        the new point(s) generated from the given edge.
    '''
    
    v = (z2-z1) /2
    v_mod, v_phas = cmath.polar(v)
    v_mod *= np.sqrt(2)
    
    return cmath.rect(v_mod, v_phas)*1j**0.5

def partial_Levi_curve(T):
    '''
    Generates the next generation of the fractal curve algorithm. For each pair of consecutive points of T
    that make a vertice, we add between those two points the new point(s) generated by the "compute_new"
    method.    

    Parameters
    ----------
    T : list[complex]
        the current state of the algorithm.

    Returns
    -------
    res : list[complex]
        The next step of the algorithm.
    '''
    
    res = []
    for i in range(len(T)-1):
        res.append(T[i])
        res.append(T[i]+compute_new(T[i], T[i+1], i%2==0))
    res.append(T[-1])
    return res

def Levi_curve(n):
    '''
    Recursive method to build the final fractal curve of degree n.

    Parameters
    ----------
    n : int
        The degree of the fractal curve to build.

    Returns
    -------
    list[complex]
        The list of complex numbers that represent the n-th degree fractal curve.
    '''
    
    if n==0 :
        return [complex(0,0), complex(1,0)]
    else :
        return partial_Levi_curve(Levi_curve(n-1))

def pts_num(n):
    '''
    Optional function that returns the amount of points in the n-th generation of the algorithm.

    Parameters
    ----------
    n : int
        The degree of the fractal curve.

    Returns
    -------
    int
        The number of vertices needed to build the n-th degree fractal curve.
    '''
    
    n0 = 2
    return (2**n)*(n0-1)+1

#  _            _       
# | |          | |      
# | |_ ___  ___| |_ ___ 
# | __/ _ \/ __| __/ __|
# | ||  __/\__ \ |_\__ \
#  \__\___||___/\__|___/

if __name__ == '__main__' :
    import matplotlib.pyplot as plt
    
    N = 20
    L = Levi_curve(N)
    
    def plot(C_List):
        Lr = [el.real for el in C_List]
        Li = [el.imag for el in C_List]
        plt.plot(Lr, Li, lw=np.exp(-N/7))
    
    plot(L)
    
    plt.axis('equal')
    plt.show()