/*
 * This file is part of the Amalthea library.
 * Copyright (C) 2025 Eugene 'Vindex' Stulin
 * Distributed under the BSL 1.0 or the GNU LGPL 3.0 or later.
 */

/**
 * The module provides the Optional data type.
 */
module amalthea.optional;

import std.format : format;


/*******************************************************************************
 * A special data type that either stores a value or does not store a value.
 */
struct Optional(T) {
    private bool state = false;
    private T base;

    /// Constructs an Optional object initialized with the given value.
    this(T value) {
        set(value);
    }

    /// Returns true if the object contains a value, otherwise false.
    bool hasValue() nothrow const {
        return this.state;
    }

    /**
     * Returns the stored value if present, otherwise `defaultValue`.
     * It's recommended to check for the presence of a value using hasValue().
     */
    T get(T defaultValue = T.init) {
        return this.hasValue() ? this.base : defaultValue;
    }

    /// Sets a new value.
    void set(T value) {
        this.base = value;
        this.state = true;
    }

    /// Returns a string representation.
    string toString() const {
        if (!this.hasValue()) {
            return "Optional(false)";
        }
        return format!"Optional(true, %s)"(base);
    }

    /// The Optional object can behave like associated value of type T.
    alias base this;
}


/// Creates a new Optional object initialized with value.
Optional!T optional(T)(T value) {
    Optional!T obj;
    obj.set(value);
    return obj;
}


/// Creates an empty Optional object.
Optional!T optional(T)() {
    Optional!T obj;
    return obj;
}


unittest {
    import std.stdio : writeln;
    import amalthea.decimal : Decimal;

    auto dec = optional(Decimal(50));
    assert(dec.hasValue());
    assert(dec == 50);  // "alias base this" in action

    auto dec2 = optional!Decimal;  // empty
    assert(!dec2.hasValue());
    auto gottenDec = dec2.get(Decimal(70));
    assert(gottenDec == 70);

    auto x = optional!int(7);
    int y = 3;
    assert(x + y == 10);

    auto z = optional!int;
    assert(!z.hasValue());
    assert(z.get() == 0);
    assert(z.get(9) == 9);
}
