Simple Trendlines
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © HoanGhetti

//@version=5

// @description An accessible and semi-effortless way to draw trendlines with automatic slope and angle calculation.
library("SimpleTrendlines", overlay = true)

// @type The object containing the essential values for proper library execution.
// @field x_axis The x-axis provided by the user to determine the distance between point A and point B
// @field offset The offset from x2 and the current bar_index. Used in situations where conditions execute ahead of where the x2 location is, such as pivot events.
// @field strictMode Strict mode works in the backend to ensure that price hasn't closed below or above the trendline before the trendline is drawn.
// @field strictType 0 if price is above line, 1 if price is below line.
export type TrendlineSettings
    int x_axis
    int offset
    bool strictMode
    int strictType


// @type The object containing values that the user can use for further calculation.
// @field slope The slope of the initial line.
// @field x1 The bar_index value of point A.
// @field x2 The bar_index value of point B.
// @field changeInX How many bars since the bar_index value of point B.
export type TrendlineData
    float slope
    int x1
    int x2
    float y1
    float y2
    int changeInX


// @type The object containing both the start line and trend line for manipulation.
// @field startline The initial line that gets drawn when instantiating the drawLine() method. 
// @field trendline The trendline that gets drawn when instantiating the drawTrendline() method. 
export type TrendlineLines
    line startline
    line trendline

// @type The object that serves as the class of the library. Inherits all properties and methods.
// @field info Contains properties inside the TrendlineSettings object.
// @field values Contains properties inside the TrendlineData object.
// @field lines Contains properties inside the TrendlineLines object.
export type Trendline
    TrendlineSettings info
    TrendlineData values
    TrendlineLines lines

// @function Creates an instance of the trendline library, accepting parameters that allow the library to function accordingly.
// @param x_axis The x-axis distance between point A and point B.
// @param offset The offset from x2 and the current bar_index. Used in situations where conditions execute ahead of where the x2 location is, such as pivot events.
// @param strictMode Strict mode works in the backend of things to ensure that price hasn't closed below the trendline before the trendline is drawn.
// @param strictType 0 ensures that price during slope calculation is above line, 1 ensures that price during slope calculation is below line.
export new(int x_axis, int offset = 0, bool strictMode = na, int strictType = na) =>
    var line l1 = line.new(na, na, na, na)
    var line l2 = line.new(na, na, na, na)
    Trendline this = Trendline.new(
         TrendlineSettings.new(x_axis, offset, strictMode, strictType),
         TrendlineData.new(na, bar_index - (x_axis + offset), bar_index - offset, na, na, na), 
         TrendlineLines.new(l1, l2)
         )
    switch 
        strictType > 1 or strictType < 0 => 
            runtime.error('strictType must be a value of 0 or 1.')
        strictType > 0 and (na(strictMode) or not strictMode) => 
            runtime.error('strictType can\'t have an assigned value without strictMode being true.')
        strictMode and offset < 1 => 
            runtime.error('Offset must be over 0 in order to use strictMode.')
    this


// @function Draws a new line from the given y-value parameters based on a condition.
// @param condition The condition in order to draw a new line.
// @param y1 The y-value of point A.
// @param y2 the y-value of point B.
// @param src Determines which value strict mode will actively check for leakage before a trendline is drawn.
export method drawLine(Trendline this, bool condition, float y1, float y2, float src = na) =>
    var float savedSlope = na
    var float savedY1 = na
    var float savedY2 = na
    if condition and (na(this.info.strictMode) or not this.info.strictMode)
        this.lines.startline.set_xy1(this.values.x1, y1)
        this.lines.startline.set_xy2(this.values.x2, y2)
        savedSlope := (this.lines.startline.get_y2() - this.lines.startline.get_y1()) / this.info.x_axis
        savedY1 := this.lines.startline.get_y1()
        savedY2 := this.lines.startline.get_y2()
    if condition and this.info.strictMode
        this.values.slope := (y2 - y1) / this.info.x_axis
        bool validElements = na
        for i = 0 to this.info.offset
            j = this.info.offset - i
            if this.info.strictType == 0 ? (na(src) ? close[j] : src[j]) >= y2 + (this.values.slope * (i)) : (na(src) ? close[j] : src[j]) <= y2 + (this.values.slope * (i))
                validElements := true
            else
                validElements := na
                break
        if not na(validElements)
            this.lines.startline.set_xy1(this.values.x1, y1)
            this.lines.startline.set_xy2(this.values.x2, y2)
            savedSlope := (this.lines.startline.get_y2() - this.lines.startline.get_y1()) / this.info.x_axis
            savedY1 := this.lines.startline.get_y1()
            savedY2 := this.lines.startline.get_y2()
    this.values.slope := savedSlope 
    this.values.y1 := savedY1
    this.values.y2 := savedY2
    this.values.changeInX := ta.barssince(ta.change(this.lines.startline.get_y1())) + this.info.offset  


// @function Draws a trendline from the line generated from the drawLine() method.
// @param condition The conditon to maintain the trendline.
export method drawTrendline(Trendline this, bool condition) =>
    if condition
        this.lines.trendline.set_xy1(this.lines.startline.get_x2(), this.lines.startline.get_y2())
        this.lines.trendline.set_xy2(bar_index, this.lines.startline.get_y2() + (this.values.slope * this.values.changeInX))