# User defined types are made with the struct keyword. Member variables are
# listed in the struct body as follows.
# Keep in mind that julia does not support redefining of types by default,
# so whenever you change anything in the struct (field names/types, etc.)
# you have to restart the REPL.
# Alternatively you could look into the package Revise.jl which solves
# some of these problems.
struct TestType
    x
    y
    z
end

# Every struct gets a default constructor that is the typename taking
# in all the member variables in order.
t = TestType(1, 2, 3)

# Every struct also gets a default print function that shows the variable
# in style of the constructor.
@show t

# Accessing the member variables of the struct is done simply by just
# writing variable_name.field_name
# every field is always public as julia does not implement any heavy
# object oriented design.
@show t.x

# This line is illegal since struct are immutable by default.
# t.y = 5

# Field mutability can be achieved by adding the keyword "mutable" in front.
# However for simple structs, like this xyz-point like structure, it is
# much more efficient if you can avoid making it mutable. This has to do
# with how mutable struct are allocated differently then immutable ones.
# Read more in the "Types" section of the julia documentation.
mutable struct TestType2
    x::Float64
    y::Float64
    z::Float64
end

# Also as illustrated above, the fields in a struct can be strictly typed.
# This is usually a good idea since it makes the struct take a constant
# amount of space, and removes a lot of type bookkeping.

t2 = TestType2(1.68, 3.14, 2.71)

# With this mutable struct, the variable now behaves more like a general
# container (Array/vector) so you can change the fields.
t2.y = 1.23
@show t2

# You can also make parametric types, kind of like templates in C++.
# This essentially creates a new type for each new T, so it alleviates the
# problem of keeping track of the types of the individual fields.
struct TestType3{T}
    x::T
    y::T
    z::T
end

# This now constructs a TestType3{Int64}
t3 = TestType3(1, 2, 3)
@show t3


# This will be the example struct throughout the rest of the file.
struct Polar{T<:Real}
    r::T
    θ::T
    
    # You might want to make your own constructors for a type.
    # These are typically made inside the struct body since this overrides
    # the creation of the default constructor described above.
    function Polar(r::T, θ::T) where T <: Real
        # Doing some constructy stuff
        println("Creating Polar number (r = $r, θ = $θ)")
        
        # The actual variable is created by calling the "new()" function
        # which acts as the default constructor. This however is just
        # accessible to functions defined inside the struct body.
        new{T}(r, θ)
    end
    
    # You might want to implement multiple different constructors.
    # This one is short and simple so it can be created with the inline syntax.
    # The zero(T) function finds the apropriate "zero" value
    # for the given type T. That way no implicit conversions have to be done.
    Polar{T}() where T <: Real = new{T}(zero(T), zero(T))
end

# Not all constructors have to be defined inside the struct body, but here
# outside the body we no longer have access to the "new()" function since
# the compiler doesn't have any way to infer which type "new" would refer
# to out here. So instead we have to use one of the constructors we defined
# inside the struct.
# This constructor is very similar to the Polar{T}() function but instead
# of writing p = Polar{Int}() to take the type in as a template argument
# you would pass the type in as an actual argument; p = Polar(Int)
Polar(::Type{T}) where T <: Real = Polar{T}()


# Constructing with the first constructor
p = Polar(3.14, 2.71)
@show p

# Constructing with the empty constructor
p = Polar{Int}()
@show p

# Constructing with the constructor taking the type as an argument
p = Polar(Float16)
@show p


# You might want to overload som basic operators and functions on your
# type. One common thing to want to override is how your variables are printed.
# Since there are so many different ways of converting a variable to text
# (print, println, @printf, string, @show, display, etc.)
# it can be difficult to find out which function is actually responsible
# for converting to text.
# This function turns out to be the Base.show() function.
# It takes in an IO stream object and and the variable you want to show.

# Since the funtion is from the Base module
# we have to override Base.show specifically
# Also it is very important to actually specify the type of the variable here
# so that we are actually specifying the show function for our type only
# and not for any general type.
function Base.show(io::IO, p::Polar)
    # Say we want to print our polar number as r * e^(i θ) style string
    show(io, p.r) # non strings we want to recursively show
    write(io, " ⋅ ℯ^(i ⋅ ") # strings we want to write directly with write
    show(io, p.θ)
    write(io, ")")
end

# Now our polar numbers are printed as we specified
p = Polar(3.14, 2.71)
@show p
println(p)
display(p)

# Even converting to a string is now done with our show functon
s = string(p)
@show s


# Overloading operators is even simpler as every infix operator is just a
# function; a + b is equivalent to +(a, b) (for all you lisp lovers)

# For some reason we cannot write the Base.* inline for infix operators
import Base.*
*(p1::Polar{T}, p2::Polar{T}) where T = Polar(p1.r * p2.r, p1.θ + p2.θ)

# Multiplication between different types might also be useful
*(x::T, p::Polar{T}) where T = Polar(x * p.r, p.θ)
# Implementing the reverse mulitplication such that it becomes commutative
*(p::Polar{T}, x::T) where T = x * p

p1 = Polar(2.0, 15.0)
p2 = Polar(3.0, 30.0)
p3 = p1 * p2
@show p3

# The in-place arithmetic operators (+=, -=, *=, /=, etc.) are not actual
# operators but rather just an alias for a = a * b so they need not be
# implemented seperately. If the memory copying is a problem and you
# really have to do it in-place the way to do that would be to make some
# sort of mult!(p, x) function.
p3 *= 0.5
@show p3