#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) out vec4 outColour;
layout(location = 1) out vec4 pickOutColour;
layout(location = 0) in vec3 fragColour;
layout(location = 1) in vec2 fragTexCoord;
layout(location = 2) in flat uint fragTexIndex; // aka customData1
layout(location = 3) in vec3 fragExtraColour;
layout(location = 4) in flat uint fragExtraFlags; // aka customData2
layout(location = 5) in flat uint fragTileIndex; // aka customData3
layout(set = 1, binding = 1) uniform sampler2D texSampler[2];

layout(binding = 0) uniform UniformBufferObject {
    mat4 normalMat;
    mat4 view;
    mat4 proj;
} ubo;

layout(set = 1, binding = 2) uniform UniformBufferObjectDynamic {
    uint isDisabled;
    uint isHovered;
    uint isHighlighted;
    uint customData;
} ubod;

vec3 interpolateColour(vec3 colour1, vec3 colour2, float factor) {
    vec3 result = colour1;
    for (uint i = 0; i < 3; i++) {
        result[i] = round(result[i] + factor * (colour2[i] - colour1[i]));
    }
    return result;
}

vec3 convertIntToColour(uint number) {
    vec3 retVal = vec3(
        (0x00FF0000 & number) >> 16,
        (0x0000FF00 & number) >> 8,
        (0x000000FF & number)
    );
    return retVal;
}

float blendDarken(float base, float blend) {
    return min(blend, base);
}

vec3 blendDarken(vec3 base, vec3 blend) {
    return vec3(blendDarken(base.r, blend.r), blendDarken(base.g, blend.g), blendDarken(base.b, blend.b));
}

vec3 blendDarken(vec3 base, vec3 blend, float opacity) {
    return (blendDarken(base, blend) * opacity + base * (1.0 - opacity));
}

bool drawStatusBarBorder() {
    bool isBorder = fragTexCoord.x < 0.01 || fragTexCoord.x > 0.99 || fragTexCoord.y < 0.08 || fragTexCoord.y > 0.92;
    if (isBorder) {
        outColour = vec4(0.22, 0.39, 0.60, 1.0);
        return true;
    }
    return false;
}

void drawStatusBarAbsolute(float status) {
    // abort if this is part of the border coordinates
    if (drawStatusBarBorder()) {
        return;
    }

    // only draw the bar as far as the current status allows
    if (status < fragTexCoord.x) {
        outColour = vec4(0, 0, 0, 0);
        return;
    }

    // determine whether or not we want to use the colour spectrum or a plain colour
    if (fragExtraColour.z == 0) {
        outColour = vec4(fragColour / 255, 1.0);
    } else {
        const vec3 statusColours[6] = vec3[6](
            vec3(255, 0, 0),
            vec3(223, 131, 3),
            vec3(215, 223, 3),
            vec3(129, 164, 8),
            vec3(13, 113, 21),
            vec3(13, 113, 21)
        );
        uint index = int(round(clamp(floor(status * 5), 0, 4)));
        float factor = clamp(mod(status, 0.2) * 5, 0, 1);
        vec3 colour = interpolateColour(statusColours[index], statusColours[index + 1], factor);
        outColour = vec4(colour / 255, 1.0);
    }
}

void drawStatusBarRelative(uint barData) {
    // abort if this is part of the border coordinates
    if (drawStatusBarBorder()) {
        return;
    }

    // see comments at the top of the file to see why these are used
    vec3 left = convertIntToColour(fragTexIndex);
    vec3 centre = convertIntToColour(fragExtraFlags);
    vec3 right = convertIntToColour(fragTileIndex);
    vec3 colour = (fragTexCoord.x < 0.5)
        ? interpolateColour(left, centre, (fragTexCoord.x * 2))
        : interpolateColour(centre, right, (fragTexCoord.x - 0.5) * 2);
    outColour = vec4(colour / 255, 1.0);

    // unpack the three separate values from the 32bit unsigned int
    uint status = barData << 24 >> 24;
    uint lowerLimit = barData << 16 >> 24;
    uint upperLimit = barData << 8 >> 24;

    // draw the bar for the lower limit
    bool isWithinRange = floor(fragTexCoord.x * 100) == lowerLimit || ceil(fragTexCoord.x * 100) == lowerLimit;
    if (isWithinRange) {
        vec3 markerColour = vec3(0.0, 0.0, 0.0);
        vec3 result = blendDarken(outColour.rgb, markerColour, 0.15);
        outColour.rgb = result;
    }

    // draw the bar for the upper limit
    isWithinRange = floor(fragTexCoord.x * 100) == upperLimit || ceil(fragTexCoord.x * 100) == upperLimit;
    if (isWithinRange) {
        vec3 markerColour = vec3(0.0, 0.0, 0.0);
        vec3 result = blendDarken(outColour.rgb, markerColour, 0.15);
        outColour.rgb = result;
    }

    // draw the actual status bar
    const uint minStatus = 3;
    const uint maxStatus = 97;
    const float range = 1.75;
    status = clamp(status, minStatus, maxStatus);
    float xCoord = fragTexCoord.x * 100;
    // in order not to hae a super slim bar, use a range
    // also do not use the full height
    isWithinRange =
        xCoord > (status - range)
        && xCoord < (status + range)
        && fragTexCoord.y > 0.1 &&
        fragTexCoord.y < 0.9;
    if (isWithinRange) {
        outColour = vec4(0.22, 0.39, 0.60, 1.0);
    }
}

void main() {
    // is this the background vertex
    if (fragExtraColour == vec3(0, 0, 0)) {
        // check if the background has a texture or uses a plain colour
        if (fragTexIndex != 0) {
            outColour = vec4(texture(texSampler[0], fragTexCoord).rgb, 1.0);
        } else {
            float alpha = float(fragExtraFlags) / 100;
            outColour = vec4((fragColour / 255), alpha);
        }
    } else if (fragExtraColour == vec3(1, 0, 0)) {
        // or the text vertex
        outColour = vec4(fragColour.rgb / 255, texture(texSampler[1], fragTexCoord).r);
    } else if (fragExtraColour.y > 0) {
        // or the status bar in.. a status bar
        float status = (float(ubod.customData) / 100);

        // fragements of the bar itself
        if (fragExtraColour.y == 2) {
            // fragments of the background of the bar
            outColour = vec4(fragColour / 255, 1.0f);
        } else if (fragExtraColour.y == 1 && fragExtraColour.z == 1) {
            drawStatusBarAbsolute(status);
        } else if (fragExtraColour.y == 1 && fragExtraColour.z == 2) {
            drawStatusBarRelative(ubod.customData);
        } else {
            outColour = vec4(0, 0, 0, 0);
        }
    }

    // change the output colour depending on the active/hovered/highlighed state of the widget
    if (ubod.isDisabled != 0) {
        // if disabled, calculate a colour that is between a darkened greyscale and the original colour
        float greyScale = (outColour.r * .2126 + outColour.g * .7152 + outColour.b * .0722) * 0.6;
        outColour.rgb = (outColour.rgb + greyScale) / 2;
    }
    if (ubod.isHovered == 1 || ubod.isHighlighted == 1) {
        outColour.rgb *= 1.3;
        if (ubod.isDisabled != 0) {
            outColour.rgb *= 0.9;
        }
    }

    pickOutColour = vec4(0, 0, 0, 0);
}
