# object oriented programming - TODO

use IO.Std.write_line;

entry() is
    let int_calculator = CALCULATOR(
        [
            ("+", INTEGER_ADDITION()),
            ("-", INTEGER_SUBTRACTION()),
            ("*", INTEGER_MULTIPLICATION()),
            ("/", INTEGER_DIVISION())
        ]
    );

    write_line("1 + 2 = {int_calculator.calculate("+", 1, 2)}");
    write_line("1 - 2 = {int_calculator.calculate("-", 1, 2)}");
    write_line("1 * 2 = {int_calculator.calculate("*", 1, 2)}");
    write_line("1 / 2 = {int_calculator.calculate("/", 1, 2)}");

    write_line("1 + 2 - 3 = {int_calculator.calculate_from_memory("-", 3)}");

    let string_calculator = CALCULATOR(
        [
            ("+", STRING_CONCATENATION()),
            ("-", STRING_SUBTRACTION())
        ]
    );

    write_line("hello + world = {string_calculator.calculate("+", "hello", "world")}");
    write_line("helloworld - world = {string_calculator.calculate("-", "helloworld", "world")}");

    string_calculator.clear_memory();

    write_line("memory is cleared");
si

trait Operation[T] is
    execute(left: T, right: T) -> T;
si

class CALCULATOR[T] is
    _operations: Collections.MAP[string, Operation[T]];

    memory: T;

    init(operations: Collections.Iterable[(name: string, operation: Operation[T])]) is
        _operations = 
            Collections.MAP(
                operations | 
                    .map(on => let (name, operation) = on in Collections.KeyValuePair`2(name, operation)));
    si

    calculate(operation_name: string, left: T, right: T) -> T =>
        if _operations.contains_key(operation_name) then
            let operation = _operations[operation_name];
            memory = operation.execute(left, right);

            memory
        else
            throw System.InvalidOperationException("invalid operation {operation_name}")
        fi;


    calculate_from_memory(operation_name: string, right: T) -> T =>
        if _operations.contains_key(operation_name) then
            let operation = _operations[operation_name];
            memory = operation.execute(memory, right);

            memory
        else
            throw System.InvalidOperationException("invalid operation {operation_name}")
        fi;

    clear_memory() is
        let def: T; // uninitialized variable has default value
        memory = def;
    si
si

class INTEGER_ADDITION: Operation[int] is
    init() is si

    execute(left: int, right: int) -> int => left + right;
si

class INTEGER_SUBTRACTION: Operation[int] is
    init() is si

    execute(left: int, right: int) -> int => left - right;
si

class INTEGER_MULTIPLICATION: Operation[int] is
    init() is si

    execute(left: int, right: int) -> int => left * right;
si

class INTEGER_DIVISION: Operation[int] is
    init() is si

    execute(left: int, right: int) -> int =>
        if right == 0 then
            throw System.InvalidOperationException("division by zero");
        else
            left / right
        fi;
si

class STRING_CONCATENATION: Operation[string] is
    init() is si

    execute(left: string, right: string) -> string => left + right;
si

class STRING_SUBTRACTION: Operation[string] is
    init() is si

    execute(left: string, right: string) -> string => left.replace(right, "");
si