/*
Copyright 2020 Lucas Heitzmann Gabrielli.
This file is part of gdstk, distributed under the terms of the
Boost Software License - Version 1.0.  See the accompanying
LICENSE file or <http://www.boost.org/LICENSE_1_0.txt>
*/

#ifndef GDSTK_HEADER_CURVE
#define GDSTK_HEADER_CURVE

#define __STDC_FORMAT_MACROS
#define _USE_MATH_DEFINES

#include <math.h>
#include <stdint.h>
#include <string.h>

#include "array.h"
#include "utils.h"

namespace gdstk {

struct CurveInstruction {
    union {
        char command;
        double number;
    };
};

// Curves can used to build complex shapes by concatenating straight or curved
// sections.  Once complete, their point_array can be used to create a polygon.
// They are also used to build the FlexPath spine.
struct Curve {
    // Before appending any section to a curve, it must contain at least 1
    // point in its point_array
    Array<Vec2> point_array;

    // Tolerance for approximating curved sections with straight lines
    double tolerance;

    // Used internally.  Keep record of the last Bézier control point, which is
    // used for smooth continuations.
    Vec2 last_ctrl;

    // Used by the python interface to store the associated PyObject* (if any).
    // No functions in gdstk namespace should touch this value!
    void* owner;

    void print(bool all) const;

    // Point array must be in a valid state before initialization
    void init(const Vec2 initial_position, double tolerance_) {
        point_array.append(initial_position);
        tolerance = tolerance_;
    }

    // This curve instance must be zeroed before copy_from
    void copy_from(const Curve& curve) {
        point_array.copy_from(curve.point_array);
        tolerance = curve.tolerance;
        last_ctrl = curve.last_ctrl;
    }

    void clear() { point_array.clear(); }

    void append(const Vec2 v) { point_array.append(v); }

    void append_unsafe(const Vec2 v) { point_array.append_unsafe(v); }

    void remove(uint64_t index) { point_array.remove(index); }

    void ensure_slots(uint64_t free_slots) { point_array.ensure_slots(free_slots); }

    // A curve is considered closed if the distance between the first and last
    // points is less then of equal to tolerance
    bool closed() const {
        if (point_array.count < 2) return false;
        const Vec2 v = point_array[0] - point_array[point_array.count - 1];
        return v.length_sq() <= tolerance * tolerance;
    }

    // In the following functions, the flag relative indicates whether the
    // points are given relative to the last point in the curve.  If multiple
    // coordinates are used, all coordinates are relative to the same reference
    // (last curve point at the time of execution)
    void horizontal(double coord_x, bool relative);
    void horizontal(const Array<double> coord_x, bool relative);
    void vertical(double coord_y, bool relative);
    void vertical(const Array<double> coord_y, bool relative);
    void segment(Vec2 end_point, bool relative);
    void segment(const Array<Vec2> points, bool relative);

    // Every 3 points define a cubic Bézier section with 2 control points
    // followed by the section end point (starting point being the current last
    // point of the curve)
    void cubic(const Array<Vec2> points, bool relative);

    // Every 2 points define a cubic Bézier section with 1 control point
    // followed by the section end point (starting point being the current last
    // point of the curve and the first control calculated from the previous
    // curve section for continuity)
    void cubic_smooth(const Array<Vec2> points, bool relative);

    // Every 2 points define a quadratic Bézier section with 1 control point
    // followed by the section end point (starting point being the current last
    // point of the curve)
    void quadratic(const Array<Vec2> points, bool relative);

    // Quadratic Bézier section with control point calculated from the previous
    // curve section for continuity
    void quadratic_smooth(Vec2 end_point, bool relative);

    // Every point is used as the end point of a quadratic Bézier section (the
    // control point is calculated from the previous curve section for
    // continuity)
    void quadratic_smooth(const Array<Vec2> points, bool relative);

    // Single Bézier section defined by any number of control points
    void bezier(const Array<Vec2> points, bool relative);

    // Create a smooth interpolation curve through points using cubic Bézier
    // sections.  The angle at any point i can be constrained to the value
    // angles[i] if angle_constraints[i] is true.  The tension array controls
    // the input and output tensions of the curve at each point (the input at
    // the first point and output at the last point are only meaningful for
    // closed curves).  Because the first point of the interpolation is the
    // last point in the curve, the lengths of the arrays angles,
    // angle_constraints, and tension must be points.count + 1.  No argument
    // can be NULL.
    void interpolation(const Array<Vec2> points, double* angles, bool* angle_constraints,
                       Vec2* tension, double initial_curl, double final_curl, bool cycle,
                       bool relative);

    // Add an elliptical arc to the curve. Argument rotation is used to rotate
    // the axes of the ellipse.
    void arc(double radius_x, double radius_y, double initial_angle, double final_angle,
             double rotation);

    // Add a parametric curve section to the curve.  If relative is true,
    // curve_function(0, data) should be (0, 0) for the curve to be continuous.
    void parametric(ParametricVec2 curve_function, void* data, bool relative);

    // Short-hand function for appending several sections at once.  Array items
    // must be formed by a series of instruction characters followed by the
    // correct number of arguments for that instruction.  Instruction
    // characters and arguments are:
    // - Line segment:            'L', x, y
    // - Horizontal segment:      'H', x
    // - Vertical segment:        'V', y
    // - Cubic Bézier:            'C', x0, y0, x1, y1, x2, y2
    // - Smooth cubic Bézier:     'S', x0, y0, x1, y1
    // - Quadratic Bézier:        'Q', x0, y0, x1, y1
    // - Smooth quadratic Bézier: 'T', x, y
    // - Elliptical arc:          'E', rad0, rad1, angle0, angle1, rotation
    // - Circular arc:            'A', radius, angle0, angle1
    // - Circular turn:           'a', radius, angle
    // Coordinates in instructions L, H, V, C, S, Q and T are absolute.  Lower
    // case versions of those instructions can be used for relative coordinates
    // (in this case, each section will be relative to the previous).  Return
    // the number of items processed.  If n < count, item n could not be
    // parsed.
    uint64_t commands(const CurveInstruction* items, uint64_t count);

    // Add a circular arc to the curve ensuring continuity.  Positive
    // (negative) angles create counter-clockwise (clockwise) turns.
    void turn(double radius, double angle) {
        const Vec2 direction = point_array[point_array.count - 1] - last_ctrl;
        double initial_angle = direction.angle() + (angle < 0 ? 0.5 * M_PI : -0.5 * M_PI);
        arc(radius, radius, initial_angle, initial_angle + angle, 0);
    }

   private:
    void append_cubic(const Vec2 p0, const Vec2 p1, const Vec2 p2, const Vec2 p3);
    void append_quad(const Vec2 p0, const Vec2 p1, const Vec2 p2);
    void append_bezier(const Array<Vec2> ctrl);
};

}  // namespace gdstk

#endif
