pub trait Template {
    fn lib(project: &str) -> String;
    fn bin(project: &str) -> String;

    fn create(project: &str, lib: bool) -> String {
        if lib {
            Self::lib(project)
        } else {
            Self::bin(project)
        }
    }
}

pub struct Dsa;
pub struct Dsc;

impl Template for Dsa {
    fn lib(project: &str) -> String {
        format!(
            r#"//
lib.dsa
// usage:
//
// include {project} "<relative path>"
//
// usage for {project}_main:
// push (arg1)
// push (arg0)
// call {project}::{project}_main
// pop (arg0)
// pop (arg1)

// Example data declarations
// dw example_data: 0x0000

// Main function template
{project}_main:
    // the correct way to start a function as defined by the calling convention
    push bpr
    mov spr, bpr

    // explanation of how to access args
    ldw bpr, rg0, 8  // arg 0
    ldw bpr, rg0, 12 // arg 1

    // your code goes here
    // Example: load example_data into rg1
    // ldw example_data, rg1

    // the correct way to end a function as defined by the calling convention
    mov bpr, spr
    pop bpr
    return
"#,
        )
    }

    fn bin(project: &str) -> String {
        format!(
            r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}

// Imports
include print: "./lib/print.dsa"

// Globals & Reserved Memory
dw stack: 0x10000
db message: "Process Exited with code:"

// Entry Point
_init:
    ldw stack, bpr
    mov bpr, spr
    push zero
    call main
    call print::print_newline
    lwi message, rg0
    push rg0
    call print::print
    pop zero
    call print::print_hex_word
    pop zero
    hlt

main:
    push bpr
    mov spr, bpr

    // Your code goes here

    // Return zero
    stw zero, bpr, 8

    mov bpr, spr
    pop bpr
    return"#,
            timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
        )
    }
}

impl Template for Dsc {
    fn lib(project: &str) -> String {
        format!(
            r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}

// Imports
include print: "./lib/print.dsa";

// Main Function
fn {project}_main() -> u32 {{
    return 0;
}}"#,
            timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
        )
    }

    fn bin(project: &str) -> String {
        format!(
            r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}

// Imports
include print: "./lib/print.dsa";

// Main Function
fn main() -> u32 {{
    return 0;
}}"#,
            timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
        )
    }
}

pub fn create_print_lib() -> String {
    String::from(
        r#"
// lib:
// print.dsa

// usage:
//
// include print "<relative path>""
//
// usage for print:
//      push (register containing address of string)
//      push pcx
//      jmp print::print
//
// usage for reset:
//      push pcx
//      jmp print::reset
//
// usage for clear:
//      push pcx
//      jmp print::clear
//
// usage for print_byte:
//      push (register containing byte)
//      push pcx
//      jmp print::print_byte
//
// usage for print_word:
//      push (register containing word)
//      push pcx
//      jmp print::print_word
//
// usage for print_num:
//      push (register containing number to print in decimal)
//      push pcx
//      jmp print::print_num
//

include maths "./maths.dsa"

dw display: 0x20000
dw current: 0x20000

// ------------------------------------------
// prints the string at addr(arg[0]) to the screen. (no trailing whitespace unless explicitly provided)
print:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8
    ldw current, rg1

_print_loop:
    ldb rg0, acc
    cmp acc, zero
    jeq _end
    stb acc, rg1

    addi rg0, 1
    addi rg1, 1

    jmp _print_loop

// ------------------------------------------
println:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8
    ldw current, rg1

_println_loop:
    ldb rg0, acc
    cmp acc, zero
    jeq _println_end
    stb acc, rg1

    addi rg0, 1
    addi rg1, 1

    jmp _println_loop

_println_end:
    call print_newline
    jmp _end

// ------------------------------------------
// prints the value of arg[0] to the screen.
print_word:
    // initialise
    push bpr
    mov spr, bpr

    // load byte into acc
    ldw bpr, rg0, 8
    ldw current, rg1

    addi rg1, 3

    stb rg0, rg1
    subi rg1, 1
    shr rg0, 8
    stb rg0, rg1
    subi rg1, 1
    shr rg0, 8
    stb rg0, rg1
    subi rg1, 1
    shr rg0, 8
    stb rg0, rg1

    addi rg1, 4
    jmp _end

// ------------------------------------------
// prints the last byte of arg[0] to the screen.
print_byte:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8
    ldw current, rg1

    stb rg0, rg1
    addi rg1, 1
    jmp _end

// ------------------------------------------
// prints the value of arg[0] to the screen in hex.
print_hex_word:
    push bpr
    mov spr, bpr

    ldw current, rg1

    ldb bpr, rg0, 8
    push rg0
    call _print_hex_byte
    addi spr, 4

    ldb bpr, rg0, 9
    push rg0
    call _print_hex_byte
    addi spr, 4

    ldb bpr, rg0, 10
    push rg0
    call _print_hex_byte
    addi spr, 4

    ldb bpr, rg0, 11
    push rg0
    call _print_hex_byte
    addi spr, 4

    jmp _end

// ------------------------------------------
// prints the last byte of arg[0] to the screen in hex.
print_hex_byte:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8
    ldw current, rg1

    call _print_hex_byte
    jmp _end

// function body
_print_hex_byte:
    // mask to get lower nibble
    lli 0xF, rg2
    // save rg0 state
    push rg0

    shr rg0, 4
    and rg0, rg2, rg0
    call _print_hex_nibble
    pop rg0

    and rg0, rg2, rg0
    call _print_hex_nibble
    return

// print a hex digit
_print_hex_nibble:
    lli 10, rg3
    cmp rg0, rg3
    jlt _print_hex_nibble_number
    addi rg0, 0x37, rg0
    stb rg0, rg1
    addi rg1, 1
    return

// helper function.
_print_hex_nibble_number:
    addi rg0, 0x30, rg0
    stb rg0, rg1
    addi rg1, 1
    return

// ------------------------------------------
// print whitespace
print_whitespace:
    push bpr
    mov spr, bpr

    ldw current, rg1
    lli 0x20, rg0
    stb rg0, rg1
    addi rg1, 1
    jmp _end

// ------------------------------------------
// print newline
print_newline:
    push bpr
    mov spr, bpr

    // load variables into registers
    ldw display, rg0
    ldw current, rg1

    // get the offset from the display base
    sub rg1, rg0, rg0

    lwi 80, rg2
    pusha 3
    push rg0
    push rg2
    call maths::divmod
    pop zero  // result
    pop rg3 // remainder
    popa 3

    sub rg1, rg3, rg2
    addi rg2, 80, rg1

    // _end saves the display state
    jmp _end

// ------------------------------------------
// prints arg[0] as a decimal number to the screen.
print_num:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8      // load number to print
    lli 0, rg5           // rg5 = digit counter

    // check if number is zero
    cmp rg0, zero
    jne _print_num_extract_digits

    // special case: print '0' for zero
    lli 0x30, rg6
    push rg6             // push digit to stack buffer
    lli 1, rg5           // we have 1 digit
    jmp _print_num_output

_print_num_extract_digits:
    // divide by 10 repeatedly to get digits
    cmp rg0, zero
    jeq _print_num_output

    // call divmod(rg0, 10)
    push rg0             // dividend
    lli 10, rg1
    push rg1             // divisor (10)
    call maths::divmod
    pop rg0              // quotient (continue dividing this)
    pop rg1              // remainder (the digit)

    // convert digit to ASCII and push to stack buffer
    addi rg1, 0x30, rg6  // convert to ASCII
    push rg6             // push digit to stack
    inc rg5              // increment digit counter

    jmp _print_num_extract_digits

_print_num_output:
    // now print digits (pop them off in reverse order)
    ldw current, rg1     // get display pointer

_print_num_output_loop:
    // check if we've printed all digits
    cmp rg5, zero
    jeq _print_num_done

    // pop digit and print it
    pop rg6
    stb rg6, rg1
    addi rg1, 1
    dec rg5

    jmp _print_num_output_loop

_print_num_done:
    jmp _end

// ------------------------------------------
// resets the cursor position on the screen to 0x20000. (0,0)
reset:
    push bpr
    mov spr, bpr
    ldw display, rg1
    jmp _end

// ------------------------------------------
// clears the screen
clear:
    push bpr
    mov spr, bpr
    // display size = 2000 bytes / 500 words
    lli 500 rg0
    ldw display, rg1

_clear_loop:
    dec rg0
    stw zero, rg1
    addi rg1, 4
    cmp rg0, zero
    jgt _clear_loop
    jmp _end

// ------------------------------------------
// return
_end:
    stw rg1, current

    mov bpr, spr
    pop bpr
    return
"#,
    )
}

pub fn create_maths_lib() -> String {
    String::from(
        r#"
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)

multiply:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8  // load op 2
    ldw bpr, rg1, 12 // load op 1
    lwi 0, rg2       // initialise rg2 to zero

_multiply_loop:
    add rg2, rg0, rg2
    dec rg1

    cmp rg1, zero
    jgt _multiply_loop

_multiply_end:
    stw rg2, bpr, 8

    mov bpr, spr
    pop bpr
    return

divmod:
    push bpr
    mov spr, bpr

    ldw bpr, rg1, 8  // load op 2
    ldw bpr, rg0, 12 // load op 1

    lli 0, rg3

_divmod_loop:
    cmp rg0, rg1
    jlt _divmod_end

    sub rg0, rg1, rg0
    inc rg3

    jmp _divmod_loop

_divmod_end:
    // store div in first arg
    // store mod in second arg
    stw rg3, bpr, 8
    stw rg0, bpr, 12

    mov bpr, spr
    pop bpr
    return

// multiply.dsa - improved version
// Multiplies two 32-bit numbers using shift-and-add
//
// Usage:
// push operand2  (multiplier)
// push operand1  (multiplicand)
// call multiply::multiply
// pop result
// pop zero       (discard second argument)

new_multiply:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8     // rg0 = multiplicand
    ldw bpr, rg1, 12    // rg1 = multiplier

    lli 0, rg2          // rg2 = result (accumulator)
    lli 32, rg3         // rg3 = bit counter

mult_loop:
    // Check if lowest bit of multiplier is 1
    lli 1, acc
    and rg1, acc, acc   // acc = rg1 & 1
    cmp acc, zero
    jeq skip_add        // if (rg1 & 1) == 0, skip addition

    // Add multiplicand to result
    add rg2, rg0, rg2

skip_add:
    shl rg0, 1          // shift multiplicand left
    shr rg1, 1          // shift multiplier right

    dec rg3
    cmp rg3, zero
    jgt mult_loop

    stw rg2, bpr, 8     // store result
    mov bpr, spr
    pop bpr
    return
"#,
    )
}
