/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include shared,rect,ellipse

// For edges, the colors are the same. For corners, these
// are the colors of each edge making up the corner.
flat varying mediump vec4 vColor00;
flat varying mediump vec4 vColor01;
flat varying mediump vec4 vColor10;
flat varying mediump vec4 vColor11;

// A point + tangent defining the line where the edge
// transition occurs. Used for corners only.
flat varying highp vec4 vColorLine;

// x: segment, y: clip mode
// We cast these to/from floats rather than using an ivec due to a driver bug
// on Adreno 3xx. See bug 1730458.
flat varying mediump vec2 vSegmentClipMode;
// x, y: styles, z, w: edge axes
// We cast these to/from floats rather than using an ivec (and bitshifting)
// due to a driver bug on Adreno 3xx. See bug 1730458.
flat varying mediump vec4 vStyleEdgeAxis;

// xy = Local space position of the clip center.
// zw = Scale the rect origin by this to get the outer
// corner from the segment rectangle.
flat varying highp vec4 vClipCenter_Sign;

// An outer and inner elliptical radii for border
// corner clipping.
flat varying highp vec4 vClipRadii;

// Reference point for determine edge clip lines.
flat varying highp vec4 vEdgeReference;

// Stores widths/2 and widths/3 to save doing this in FS.
flat varying highp vec4 vPartialWidths;

// Clipping parameters for dot or dash.
flat varying mediump vec4 vClipParams1;
flat varying mediump vec4 vClipParams2;

// Local space position
varying highp vec2 vPos;

#define SEGMENT_TOP_LEFT        0
#define SEGMENT_TOP_RIGHT       1
#define SEGMENT_BOTTOM_RIGHT    2
#define SEGMENT_BOTTOM_LEFT     3
#define SEGMENT_LEFT            4
#define SEGMENT_TOP             5
#define SEGMENT_RIGHT           6
#define SEGMENT_BOTTOM          7

// Border styles as defined in webrender_api/types.rs
#define BORDER_STYLE_NONE         0
#define BORDER_STYLE_SOLID        1
#define BORDER_STYLE_DOUBLE       2
#define BORDER_STYLE_DOTTED       3
#define BORDER_STYLE_DASHED       4
#define BORDER_STYLE_HIDDEN       5
#define BORDER_STYLE_GROOVE       6
#define BORDER_STYLE_RIDGE        7
#define BORDER_STYLE_INSET        8
#define BORDER_STYLE_OUTSET       9

#define CLIP_NONE        0
#define CLIP_DASH_CORNER 1
#define CLIP_DASH_EDGE   2
#define CLIP_DOT         3

#ifdef WR_VERTEX_SHADER

PER_INSTANCE in vec2 aTaskOrigin;
PER_INSTANCE in vec4 aRect;
PER_INSTANCE in vec4 aColor0;
PER_INSTANCE in vec4 aColor1;
PER_INSTANCE in int aFlags;
PER_INSTANCE in vec2 aWidths;
PER_INSTANCE in vec2 aRadii;
PER_INSTANCE in vec4 aClipParams1;
PER_INSTANCE in vec4 aClipParams2;

vec2 get_outer_corner_scale(int segment) {
    vec2 p;

    switch (segment) {
        case SEGMENT_TOP_LEFT:
            p = vec2(0.0, 0.0);
            break;
        case SEGMENT_TOP_RIGHT:
            p = vec2(1.0, 0.0);
            break;
        case SEGMENT_BOTTOM_RIGHT:
            p = vec2(1.0, 1.0);
            break;
        case SEGMENT_BOTTOM_LEFT:
            p = vec2(0.0, 1.0);
            break;
        default:
            // The result is only used for non-default segment cases
            p = vec2(0.0);
            break;
    }

    return p;
}

// NOTE(emilio): If you change this algorithm, do the same change
// in border.rs
vec4 mod_color(vec4 color, bool is_black, bool lighter) {
    const float light_black = 0.7;
    const float dark_black = 0.3;

    const float dark_scale = 0.66666666;
    const float light_scale = 1.0;

    if (is_black) {
        if (lighter) {
            return vec4(vec3(light_black), color.a);
        }
        return vec4(vec3(dark_black), color.a);
    }

    if (lighter) {
        return vec4(color.rgb * light_scale, color.a);
    }
    return vec4(color.rgb * dark_scale, color.a);
}

vec4[2] get_colors_for_side(vec4 color, int style) {
    vec4 result[2];

    bool is_black = color.rgb == vec3(0.0, 0.0, 0.0);

    switch (style) {
        case BORDER_STYLE_GROOVE:
            result[0] = mod_color(color, is_black, true);
            result[1] = mod_color(color, is_black, false);
            break;
        case BORDER_STYLE_RIDGE:
            result[0] = mod_color(color, is_black, false);
            result[1] = mod_color(color, is_black, true);
            break;
        default:
            result[0] = color;
            result[1] = color;
            break;
    }

    return result;
}

void main(void) {
    int segment = aFlags & 0xff;
    int style0 = (aFlags >> 8) & 0xff;
    int style1 = (aFlags >> 16) & 0xff;
    int clip_mode = (aFlags >> 24) & 0x0f;

    vec2 size = aRect.zw - aRect.xy;
    vec2 outer_scale = get_outer_corner_scale(segment);
    vec2 outer = outer_scale * size;
    vec2 clip_sign = 1.0 - 2.0 * outer_scale;

    // Set some flags used by the FS to determine the
    // orientation of the two edges in this corner.
    ivec2 edge_axis = ivec2(0, 0);
    // Derive the positions for the edge clips, which must be handled
    // differently between corners and edges.
    vec2 edge_reference = vec2(0.0);
    switch (segment) {
        case SEGMENT_TOP_LEFT:
            edge_axis = ivec2(0, 1);
            edge_reference = outer;
            break;
        case SEGMENT_TOP_RIGHT:
            edge_axis = ivec2(1, 0);
            edge_reference = vec2(outer.x - aWidths.x, outer.y);
            break;
        case SEGMENT_BOTTOM_RIGHT:
            edge_axis = ivec2(0, 1);
            edge_reference = outer - aWidths;
            break;
        case SEGMENT_BOTTOM_LEFT:
            edge_axis = ivec2(1, 0);
            edge_reference = vec2(outer.x, outer.y - aWidths.y);
            break;
        case SEGMENT_TOP:
        case SEGMENT_BOTTOM:
            edge_axis = ivec2(1, 1);
            break;
        case SEGMENT_LEFT:
        case SEGMENT_RIGHT:
        default:
            break;
    }

    vSegmentClipMode = vec2(float(segment), float(clip_mode));
    vStyleEdgeAxis = vec4(float(style0), float(style1), float(edge_axis.x), float(edge_axis.y));

    vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
    vPos = size * aPosition.xy;

    vec4[2] color0 = get_colors_for_side(aColor0, style0);
    vColor00 = color0[0];
    vColor01 = color0[1];
    vec4[2] color1 = get_colors_for_side(aColor1, style1);
    vColor10 = color1[0];
    vColor11 = color1[1];
    vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
    vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
    vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
    vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
    vClipParams1 = aClipParams1;
    vClipParams2 = aClipParams2;

    // For the case of dot and dash clips, optimize the number of pixels that
    // are hit to just include the dot itself.
    if (clip_mode == CLIP_DOT) {
        float radius = aClipParams1.z;

        // Expand by a small amount to allow room for AA around
        // the dot if it's big enough.
        if (radius > 0.5)
            radius += 2.0;

        vPos = vClipParams1.xy + radius * (2.0 * aPosition.xy - 1.0);
        vPos = clamp(vPos, vec2(0.0), size);
    } else if (clip_mode == CLIP_DASH_CORNER) {
        vec2 center = (aClipParams1.xy + aClipParams2.xy) * 0.5;
        // This is a gross approximation which works out because dashes don't have
        // a strong curvature and we will overshoot by inflating the geometry by
        // this amount on each side (sqrt(2) * length(dash) would be enough and we
        // compute 2 * approx_length(dash)).
        float dash_length = length(aClipParams1.xy - aClipParams2.xy);
        float width = max(aWidths.x, aWidths.y);
        // expand by a small amout for AA just like we do for dots.
        vec2 r = vec2(max(dash_length, width)) + 2.0;
        vPos = clamp(vPos, center - r, center + r);
    }

    gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
}
#endif

#ifdef WR_FRAGMENT_SHADER
vec4 evaluate_color_for_style_in_corner(
    vec2 clip_relative_pos,
    int style,
    vec4 color0,
    vec4 color1,
    vec4 clip_radii,
    float mix_factor,
    int segment,
    float aa_range
) {
    switch (style) {
        case BORDER_STYLE_DOUBLE: {
            // Get the distances from 0.33 of the radii, and
            // also 0.67 of the radii. Use these to form a
            // SDF subtraction which will clip out the inside
            // third of the rounded edge.
            float d_radii_a = distance_to_ellipse(
                clip_relative_pos,
                clip_radii.xy - vPartialWidths.xy
            );
            float d_radii_b = distance_to_ellipse(
                clip_relative_pos,
                clip_radii.xy - 2.0 * vPartialWidths.xy
            );
            float d = min(-d_radii_a, d_radii_b);
            color0 *= distance_aa(aa_range, d);
            break;
        }
        case BORDER_STYLE_GROOVE:
        case BORDER_STYLE_RIDGE: {
            float d = distance_to_ellipse(
                clip_relative_pos,
                clip_radii.xy - vPartialWidths.zw
            );
            float alpha = distance_aa(aa_range, d);
            float swizzled_factor;
            switch (segment) {
                case SEGMENT_TOP_LEFT: swizzled_factor = 0.0; break;
                case SEGMENT_TOP_RIGHT: swizzled_factor = mix_factor; break;
                case SEGMENT_BOTTOM_RIGHT: swizzled_factor = 1.0; break;
                case SEGMENT_BOTTOM_LEFT: swizzled_factor = 1.0 - mix_factor; break;
                default: swizzled_factor = 0.0; break;
            };
            vec4 c0 = mix(color1, color0, swizzled_factor);
            vec4 c1 = mix(color0, color1, swizzled_factor);
            color0 = mix(c0, c1, alpha);
            break;
        }
        default:
            break;
    }

    return color0;
}

vec4 evaluate_color_for_style_in_edge(
    vec2 pos_vec,
    int style,
    vec4 color0,
    vec4 color1,
    float aa_range,
    int edge_axis_id
) {
    vec2 edge_axis = edge_axis_id != 0 ? vec2(0.0, 1.0) : vec2(1.0, 0.0);
    float pos = dot(pos_vec, edge_axis);
    switch (style) {
        case BORDER_STYLE_DOUBLE: {
            float d = -1.0;
            float partial_width = dot(vPartialWidths.xy, edge_axis);
            if (partial_width >= 1.0) {
                vec2 ref = vec2(
                    dot(vEdgeReference.xy, edge_axis) + partial_width,
                    dot(vEdgeReference.zw, edge_axis) - partial_width
                );
                d = min(pos - ref.x, ref.y - pos);
            }
            color0 *= distance_aa(aa_range, d);
            break;
        }
        case BORDER_STYLE_GROOVE:
        case BORDER_STYLE_RIDGE: {
            float ref = dot(vEdgeReference.xy + vPartialWidths.zw, edge_axis);
            float d = pos - ref;
            float alpha = distance_aa(aa_range, d);
            color0 = mix(color0, color1, alpha);
            break;
        }
        default:
            break;
    }

    return color0;
}

void main(void) {
    float aa_range = compute_aa_range(vPos);
    vec4 color0, color1;

    int segment = int(vSegmentClipMode.x);
    int clip_mode = int(vSegmentClipMode.y);
    ivec2 style = ivec2(int(vStyleEdgeAxis.x), int(vStyleEdgeAxis.y));
    ivec2 edge_axis = ivec2(int(vStyleEdgeAxis.z), int(vStyleEdgeAxis.w));

    float mix_factor = 0.0;
    if (edge_axis.x != edge_axis.y) {
        float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
        mix_factor = distance_aa(aa_range, -d_line);
    }

    // Check if inside corner clip-region
    vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
    bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
    float d = -1.0;

    switch (clip_mode) {
        case CLIP_DOT: {
            // Set clip distance based or dot position and radius.
            d = distance(vClipParams1.xy, vPos) - vClipParams1.z;
            break;
        }
        case CLIP_DASH_EDGE: {
            bool is_vertical = vClipParams1.x == 0.;
            float half_dash = is_vertical ? vClipParams1.y : vClipParams1.x;
            // We want to draw something like:
            // +---+---+---+---+
            // |xxx|   |   |xxx|
            // +---+---+---+---+
            float pos = is_vertical ? vPos.y : vPos.x;
            bool in_dash = pos < half_dash || pos > 3.0 * half_dash;
            if (!in_dash) {
                d = 1.;
            }
            break;
        }
        case CLIP_DASH_CORNER: {
            // Get SDF for the two line/tangent clip lines,
            // do SDF subtract to get clip distance.
            float d0 = distance_to_line(vClipParams1.xy,
                                        vClipParams1.zw,
                                        vPos);
            float d1 = distance_to_line(vClipParams2.xy,
                                        vClipParams2.zw,
                                        vPos);
            d = max(d0, -d1);
            break;
        }
        case CLIP_NONE:
        default:
            break;
    }

    if (in_clip_region) {
        float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy);
        float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw);
        float d_radii = max(d_radii_a, -d_radii_b);
        d = max(d, d_radii);

        color0 = evaluate_color_for_style_in_corner(
            clip_relative_pos,
            style.x,
            vColor00,
            vColor01,
            vClipRadii,
            mix_factor,
            segment,
            aa_range
        );
        color1 = evaluate_color_for_style_in_corner(
            clip_relative_pos,
            style.y,
            vColor10,
            vColor11,
            vClipRadii,
            mix_factor,
            segment,
            aa_range
        );
    } else {
        color0 = evaluate_color_for_style_in_edge(
            vPos,
            style.x,
            vColor00,
            vColor01,
            aa_range,
            edge_axis.x
        );
        color1 = evaluate_color_for_style_in_edge(
            vPos,
            style.y,
            vColor10,
            vColor11,
            aa_range,
            edge_axis.y
        );
    }

    float alpha = distance_aa(aa_range, d);
    vec4 color = mix(color0, color1, mix_factor);
    oFragColor = color * alpha;
}
#endif
