module sparkline.sparkline;

import std.algorithm : max, min;
import std.array : appender;

/**
 * Generates sparkline string given arrray of numbers
 */
public string sparkline(const double[] numbers, wchar[] steps = [
        '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'
    ])
{
    if (numbers.length == 0)
    {
        return "";
    }
    import std.numeric: normalize;

    double minVal = numbers[0];
    double maxVal = numbers[0];
    const sLen = steps.length;
    for (int i = 0; i < numbers.length; i++)
    {
        minVal = min(minVal, numbers[i]);
        maxVal = max(maxVal, numbers[i]);
    }
    auto strBuilder = appender!string;
    strBuilder.reserve(numbers.length);
    for (int i = 0; i < numbers.length; i++)
    {
        if (minVal == maxVal)
        {
            strBuilder.put(steps[0]);
            continue;
        }
        int x = cast(int)(((numbers[i] - minVal) / (maxVal - minVal)) * (sLen - 1));
        strBuilder.put(steps[x]);
    }
    return strBuilder.data;
}

unittest
{
    assert(sparkline([1.0, 2.0, 3.0]) == "▁▄█");
    assert(sparkline([67, 71, 77, 85, 95, 104, 106, 105, 100, 89, 76, 66]) == "▁▁▂▄▆▇█▇▆▅▂▁");
    assert(sparkline([1.1, 0.1, 0.1, 1.1]) == "█▁▁█");
    assert(sparkline([1, 1, 1, 1, 1]) == "▁▁▁▁▁", "works with all values being equal");
    assert(sparkline([], []) == "", "works with no values");
    assert(sparkline([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
            ]) == "0123456789", "works with custom steps");
}