"""
Copyright (c) 2022, Jose Luis Cercos-Pita <jlc@core-marine.com>

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

LEVEL_NONE = 4096
LEVEL_ERR = 3
LEVEL_WRN = 2
LEVEL_MSG = 1
LEVEL_DBG = 0


ERRCODE_SUCCESS = 0
ERRCODE_INVALID_INPUT_FILE = -1
ERRCODE_INVALID_OUTPUT_FILE = -2
ERRCODE_INVALID_INPUT = -3
ERRCODE_NAN_ERROR = -4
ERRCODE_MEM_ERROR = -5
ERRCODE_INVALID_VALUE = -6
ERRCODE_NON_IMPLEMENTED = -7
ERRCODE_UNHANDLED_ERROR = -255


BODY_TYPE_COUPLED = CONN_TYPE_COUPLED = -1
BODY_TYPE_FREE = CONN_TYPE_FREE = 0
BODY_TYPE_FIXED = CONN_TYPE_FIXED = 1
ROD_TYPE_COUPLED = -2
ROD_TYPE_CPLDPIN = -1
ROD_TYPE_FREE = 0
ROD_TYPE_PINNED = 1
ROD_TYPE_FIXED = 2


ENDPOINT_A = ENDPOINT_BOTTOM = 0
ENDPOINT_B = ENDPOINT_TOP = 1


#                                  MoorDyn2.h
#  =============================================================================

def Create(filepath=""):
    """Creates the MoorDyn system, loading the MoorDyn input file

    Keyword arguments:
    filepath (str): The file to load. If "" is passed, then "Mooring/lines.txt"
                    will be considered

    Returns:
    cmoordyn.MoorDyn: The MoorDyn instance
    """
    import cmoordyn
    return cmoordyn.create(filepath)


def NCoupledDOF(instance):
    """Get the number of coupled Degrees Of Freedom (DOF)

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: The number of DOFs
    """
    import cmoordyn
    return cmoordyn.n_coupled_dof(instance)


def SetVerbosity(instance, verbosity):
    """Set the verbosity level of the MoorDyn instance

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    verbosity (int): The Verbosity level. One of:
                     - moordyn.LEVEL_NONE
                     - moordyn.LEVEL_ERR
                     - moordyn.LEVEL_WRN
                     - moordyn.LEVEL_MSG
                     - moordyn.LEVEL_DBG

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    return cmoordyn.set_verbosity(instance, verbosity)


def SetLogFile(instance, filepath):
    """Set the verbosity level of the MoorDyn instance

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    filepath (str): The log file path

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    return cmoordyn.set_logfile(instance, filepath)


def SetLogLevel(instance, verbosity):
    """Set the log file verbosity level of the MoorDyn instance

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    verbosity (int): The Verbosity level. One of:
                     - moordyn.LEVEL_NONE
                     - moordyn.LEVEL_ERR
                     - moordyn.LEVEL_WRN
                     - moordyn.LEVEL_MSG
                     - moordyn.LEVEL_DBG

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    return cmoordyn.set_loglevel(instance, verbosity)


def Log(instance, level, msg):
    """Set the log file verbosity level of the MoorDyn instance

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    level (int): The Verbosity level. One of:
                     - moordyn.LEVEL_NONE
                     - moordyn.LEVEL_ERR
                     - moordyn.LEVEL_WRN
                     - moordyn.LEVEL_MSG
                     - moordyn.LEVEL_DBG
    msg (str): The message to log

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    import inspect

    head = "DBG "
    if level >= LEVEL_ERR:
        head = "ERR "
    elif level == LEVEL_WRN:
        head = "WRN "
    elif level == LEVEL_MSG:
        head = "MSG "
    caller = inspect.stack()[1]
    head = head + "{}:{} {}() : ".format(caller.filename,
                                         caller.lineno,
                                         caller.function)
    return cmoordyn.log(instance, level, head + msg)


def Init(instance, x, v):
    """Initializes the MoorDyn sytem, calculating initial conditions based on
    connection positions specified by array x and their velocities in array v

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Keyword arguments:
    x (list): Position of the coupled connections
    v (list): Velocity of the coupled connections

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    return cmoordyn.init(instance, list(x), list(v), 0)


def Init_NoIC(instance, x, v):
    """Initializes the MoorDyn sytem, without calculating initial conditions

    Use this when you are planning to call moordyn.Load() afterwards

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Keyword arguments:
    x (list): Position of the coupled connections
    v (list): Velocity of the coupled connections

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    return cmoordyn.init(instance, list(x), list(v), 1)


def Step(instance, x, v, t, dt):
    """Compute a time step

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    x (list): Position of the coupled connections
    v (list): Velocity of the coupled connections
    t (float): The time instant
    dt (float): The time step

    Returns:
    list: The forces acting on the coupled connections
    """
    import cmoordyn
    return cmoordyn.step(instance, list(x), list(v), t, dt)


def Close(instance):
    """This function deallocates the variables used by MoorDyn

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    return cmoordyn.close(instance)


def GetWaves(instance):
    """Get the wave kinematics instance

    The wave kinematics instance is only useful if WaveKin option is set to >= 2
    in the input file.

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    cmoordyn.MoorDynWaves: The waves manager
    """
    import cmoordyn
    return cmoordyn.get_waves(instance)


def ExternalWaveKinInit(instance):
    """Initializes the external Wave kinetics

    This is useless unless WaveKin option is set to 1 in the input file. If
    that is the case, remember calling this function after moordyn.Init()

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn
    return cmoordyn.ext_wave_init(instance)


def ExternalWaveKinGetN(instance):
    """Get the number of points where the waves kinematics shall be provided

    This is useless unless WaveKin option is set to 1 in the input file.

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: The number of points
    """
    import cmoordyn
    return cmoordyn.ext_wave_init(instance)


def GetWaveKinCoordinates(instance):
    """Get the points where the waves kinematics shall be provided

    The kinematics on those points shall be provided just if WaveKin is set
    to 1 in the input file

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    list (n, 3): The list of points
    """
    import cmoordyn

    coords = cmoordyn.ext_wave_coords(instance)
    n = len(coords) // 3
    points = []
    for i in range(n):
        points.append([coords[3 * i], coords[3 * i + 1], coords[3 * i + 2]])
    return points


def SetWaveKin(instance, u, a, t):
    """Set the kinematics of the waves

    Use this function if WaveKin option is set to 1 in the input file

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    u (list (n, 3)): The list of velocities evaluated in the points provided
                     by moordyn.GetWaveKinCoordinates()
    a (list (n, 3)): The list of accelerations evaluated in the points provided
                     by moordyn.GetWaveKinCoordinates()
    t (float): The simulation time

    Returns:
    int: 0 uppon success, an error code otherwise
    """
    import cmoordyn

    assert len(u) == len(a)
    uu, aa = [], []
    for i in range(len(u)):
        assert len(u[i]) == 3 and len(a[i]) == 3
        uu += u[i]
        aa += a[i]
    return cmoordyn.ext_wave_set(instance, uu, aa, t)


def GetNumberBodies(instance):
    """Get the number of bodies

    Remember that the first body index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: The number of bodies
    """
    import cmoordyn
    return cmoordyn.get_number_bodies(instance)


def GetBody(instance, body):
    """ Get a rigid body

    Remember that the first body index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    body (int): The rigid body index, starting at 1

    Returns:
    cmoordyn.MoorDynBody: The rigid body instance
    """
    import cmoordyn
    return cmoordyn.get_body(instance, body)


def GetNumberRods(instance):
    """Get the number rods

    Remember that the rod index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: The number of rods
    """
    import cmoordyn
    return cmoordyn.get_number_rods(instance)


def GetRod(instance, rod):
    """ Get a rod

    Remember that the first rod index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    rod (int): The rod index, starting at 1

    Returns:
    cmoordyn.MoorDynRod: The rod instance
    """
    import cmoordyn
    return cmoordyn.get_rod(instance, rod)


def GetNumberConnections(instance):
    """Get the number of connections

    Remember that the first connection index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: The number of connections
    """
    import cmoordyn
    return cmoordyn.get_number_connections(instance)


def GetConnection(instance, conn):
    """ Get a connection

    Remember that the first connection index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    conn (int): The connection index, starting at 1

    Returns:
    cmoordyn.MoorDynConnection: The connection instance
    """
    import cmoordyn
    return cmoordyn.get_connection(instance, conn)


def GetNumberLines(instance):
    """Get the number of mooring lines

    Remember that the first line index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance

    Returns:
    int: The number of mooring lines
    """
    import cmoordyn
    return cmoordyn.get_number_lines(instance)


def GetLine(instance, line):
    """ Get a mooring line

    Remember that the first line index is 1

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    line (int): The line index, starting at 1

    Returns:
    cmoordyn.MoorDynLine: The line instance
    """
    import cmoordyn
    return cmoordyn.get_line(instance, line)


def GetFASTtens(instance, n_lines):
    """Get the horizontal and vertical components of the fairlead and anchor
    tensions

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    n_lines (int): The number of lines

    Returns:
    list: The horizontal fairlead tension at each line
    list: The vertical fairlead tension at each line
    list: The horizontal anchor tension at each line
    list: The vertical anchor tension at each line
    """
    import cmoordyn
    data = cmoordyn.get_fast_tens(instance, n_lines)
    return data[0], data[1], data[2], data[3]


def Save(instance, filepath):
    """Save the MoorDyn system into a file that can be loaded afterwards to
    resume the simulation

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    filepath (str): The file path

    Returns:
    None
    """
    import cmoordyn
    cmoordyn.save(instance, filepath)


def Load(instance, filepath):
    """Load a previously saved MoorDyn system, in order to resume the simulation

    You still must read the same definition file with moordyn.Create() and call
    moordyn.Init_NoIC()

    Parameters:
    instance (cmoordyn.MoorDyn): The MoorDyn instance
    filepath (str): The file path

    Returns:
    None
    """
    import cmoordyn
    cmoordyn.load(instance, filepath)

#                                  Waves.h
#  =============================================================================


def GetWavesKin(instance, x, y, z, t):
    """ Get the wave kinematics

    Parameters:
    instance (cmoordyn.MoorDynWaves): The waves instance
    x (float): The x coordinate
    y (float): The y coordinate
    z (float): The z coordinate
    t (float): The simulation time

    Returns:
    u: The velocity (a tuple with 3 components)
    ud: The acceleration (a tuple with 3 components)
    zeta: The wave height
    pdyn: The dynamic pressure
    """
    import cmoordyn
    return cmoordyn.waves_getkin(instance, x, y, z, t)


#                                  Body.h
#  =============================================================================


def GetBodyID(instance):
    """ Get the body id

    Parameters:
    instance (cmoordyn.MoorDynBody): The body instance

    Returns:
    i: The body id
    """
    import cmoordyn
    return cmoordyn.body_get_id(instance)


def GetBodyType(instance):
    """ Get the body type

    Parameters:
    instance (cmoordyn.MoorDynBody): The body instance

    Returns:
    t: The body type
    """
    import cmoordyn
    return cmoordyn.body_get_type(instance)


def GetBodyState(instance):
    """ Get the body state

    Parameters:
    instance (cmoordyn.MoorDynBody): The body instance

    Returns:
    r: The position (a tuple with 3 components)
    u: The velocity (a tuple with 3 components)
    """
    import cmoordyn
    return cmoordyn.body_get_state(instance)


#                                  Rod.h
#  =============================================================================


def GetRodID(instance):
    """ Get the rod id

    Parameters:
    instance (cmoordyn.MoorDynRod): The rod instance

    Returns:
    i: The rod id
    """
    import cmoordyn
    return cmoordyn.rod_get_id(instance)


def GetRodType(instance):
    """ Get the rod id

    Parameters:
    instance (cmoordyn.MoorDynRod): The rod instance

    Returns:
    i: The rod type
    """
    import cmoordyn
    return cmoordyn.rod_get_type(instance)


def GetRodN(instance):
    """ Get the rod number of segments

    Parameters:
    instance (cmoordyn.MoorDynRod): The rod instance

    Returns:
    n: The number of segments
    """
    import cmoordyn
    return cmoordyn.rod_get_n(instance)


def GetRodNodePos(instance, i):
    """ Get a rod node position

    Parameters:
    instance (cmoordyn.MoorDynRod): The rod instance
    i (int): The node index

    Returns:
    r: The node pos
    """
    import cmoordyn
    return cmoordyn.rod_get_node_pos(instance, i)


#                                Connection.h
#  =============================================================================


def GetConnectID(instance):
    """ Get the connection id

    Parameters:
    instance (cmoordyn.MoorDynConnection): The connection instance

    Returns:
    i: The connection id
    """
    import cmoordyn
    return cmoordyn.conn_get_id(instance)


def GetConnectType(instance):
    """ Get the connection type

    Parameters:
    instance (cmoordyn.MoorDynConnection): The connection instance

    Returns:
    t: The connection type
    """
    import cmoordyn
    return cmoordyn.conn_get_type(instance)


def GetConnectPos(instance):
    """ Get the connection position

    Parameters:
    instance (cmoordyn.MoorDynConnection): The connection instance

    Returns:
    v: The connection position
    """
    import cmoordyn
    return cmoordyn.conn_get_pos(instance)


def GetConnectVel(instance):
    """ Get the connection velocity

    Parameters:
    instance (cmoordyn.MoorDynConnection): The connection instance

    Returns:
    v: The connection velocity
    """
    import cmoordyn
    return cmoordyn.conn_get_vel(instance)


def GetConnectForce(instance):
    """ Get the connection force

    Parameters:
    instance (cmoordyn.MoorDynConnection): The connection instance

    Returns:
    v: The connection force
    """
    import cmoordyn
    return cmoordyn.conn_get_force(instance)


def GetConnectNAttached(instance):
    """ Get the number of attached lines

    Parameters:
    instance (cmoordyn.MoorDynConnection): The connection instance

    Returns:
    n: The number of attached lines
    """
    import cmoordyn
    return cmoordyn.conn_get_nattached(instance)


def GetConnectAttached(instance, i):
    """ Get an attached line

    Parameters:
    instance (cmoordyn.MoorDynConnection): The connection instance
    i (int): The index of the attachment

    Returns:
    l: The attached line
    e: The endpoint
    """
    import cmoordyn
    return cmoordyn.conn_get_attached(instance, i)


#                                  Line.h
#  =============================================================================


def GetLineID(instance):
    """ Get the line id

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance

    Returns:
    i: The line id
    """
    import cmoordyn
    return cmoordyn.line_get_id(instance)


def GetLineN(instance):
    """ Get the line number of segments

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance

    Returns:
    n: The number of segments
    """
    import cmoordyn
    return cmoordyn.line_get_n(instance)


def GetLineUnstretchedLength(instance):
    """ Get the line unstretched length

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance

    Returns:
    l: The unstreched length
    """
    import cmoordyn
    return cmoordyn.line_get_ulen(instance)


def GetLineNodePos(instance, i):
    """ Get a line node position

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance
    i (int): The node index

    Returns:
    r: The node position
    """
    import cmoordyn
    return cmoordyn.line_get_node_pos(instance, i)


def GetLineNodeTen(instance, i):
    """ Get a line node tension

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance
    i (int): The node index

    Returns:
    t: The node tension
    """
    import cmoordyn
    return cmoordyn.line_get_node_force(instance, i)


def GetLineNodeCurv(instance, i):
    """ Get a line node curvature

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance
    i (int): The node index

    Returns:
    r: The node curvature
    """
    import cmoordyn
    return cmoordyn.line_get_node_curv(instance, i)


def GetLineFairTen(instance):
    """ Get the tension magnitude at the fairlead of a line

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance

    Returns:
    t: The tension magnitude
    """
    import cmoordyn
    return cmoordyn.line_get_fairlead_tension(instance)


def GetLineMaxTen(instance):
    """ Get the line maximum tension magnitude

    Parameters:
    instance (cmoordyn.MoorDynLine): The line instance

    Returns:
    t: The tension magnitude
    """
    import cmoordyn
    return cmoordyn.line_get_max_tension(instance)
