abstract type AbstractTrend end



mutable struct LinearTrend{T} <: AbstractTrend
    intercept::T
    slope::T
    mode::Int8
    internal::Bool
    step::Integer
end



function LinearTrend(intercept::Float32, slope::Float32;
                     mode::Int8=Ses.to_intmode[:add], internal_step::Bool=false)
    LinearTrend([intercept], [slope], mode, internal_step, 1)
end



function Base.show(io::IO, lt::LinearTrend)
    print(io, "Linear Trend [y = mx + b](");
    print(io, "m: ", lt.slope[1], ", b: ", lt.intercept[1]);
    print(io, "mode: ", String(Ses.to_symbmode(lt.mode)));
    if lt.internal
        print(io, "step: ", lt.step);
    end
    print(io, ")");
end



(lt::LinearTrend{T})(::V; lag::Integer = 0) where {T, V} = lt(1, lag = lag);
function (lt::LinearTrend{T})(v::AbstractMatrix{V}; lag::Integer = 0) where {T, V}
    _size = size(v)[1];
    return [lt(idx, lag = lag) for idx in 1:_size]
end
function (lt::LinearTrend{T})(v::AbstractVector{V}; lag::Integer = 0) where {T, V}
    _size = length(v);
    return [lt(idx, lag = lag) for idx in 1:_size]
end
function (lt::LinearTrend{T})(tstep::Integer; lag::Integer = 0) where {T}
    _step = if lt.internal lt.step else tstep end;
    t = (lt.slope * (_step + lag)) .+ lt.intercept;
    
    if lt.internal
        lt.step += inte_one;
    end

    return t[1];
end



@functor LinearTrend



trainable(lt::LinearTrend) = (lt.intercept, lt.slope)



restart!(lt::LinearTrend) = lt.step = inte_one;



struct LogLinearTrend{T} <: AbstractTrend
    beta0::T
    beta1::T
end



function LogLinearTrend(;beta0::Float32 = 1.0f0, beta1::Float32 = 0.0f0)
    LogLinearTrend([beta0], [beta1])
end



function Base.show(io::IO, llt::LogLinearTrend)
    print(io, "Log-Linear Trend [log(y) = β0 + β1 * x](");
    print(io, "β0: ", llt.beta0[1], ", β1: ", llt.beta1[1]);
    print(io, ")");
end



function (llt::LogLinearTrend{T})(v::AbstractVector{V}; lag::Integer = 0) where {T, V}
    _size = size(v)[1];
    return [llt(idx, lag = lag) for idx in 1:_size]
end
function (llt::LogLinearTrend{T})(tstep::Integer; lag::Integer = 0) where {T}
    t = oftype(llt.beta0, exp(llt.beta0 .+ ((tstep + lag) * llt.beta1)));
    return t[1];
end



@functor LogLinearTrend



trainable(llt::LogLinearTrend) = (llt.beta0, llt.beta1)



struct LogLogTrend{T} <: AbstractTrend
    beta0::T
    beta1::T
end



function LogLogTrend(;beta0::Float32 = 1.0f0, beta1::Float32 = 0.0f0)
    LogLogTrend([beta0], [beta1])
end



function Base.show(io::IO, llt::LogLinearTrend)
    print(io, "Log-Log Trend [log(y) = β0 + β1 * log(x)](");
    print(io, "β0: ", llt.beta0[1], ", β1: ", llt.beta1[1]);
    print(io, ")");
end



function (llt::LogLogTrend{T})(v::AbstractVector{V}; lag::Integer = 0) where {T, V}
    _size = size(v)[1];
    return llt.([1:_size;], lag = lag)
end
function (llt::LogLogTrend{T})(tstep::Integer; lag::Integer = 0) where {T}
    t = oftype(llt.beta0, llt.beta0 .+ exp((log(tstep + lag) * llt.beta1)));
    return t[1];
end



@functor LogLogTrend



trainable(llt::LogLogTrend) = (llt.beta0, llt.beta1)