using Gtk;

public struct BubbleLayoutConstants {
    public float tail_width;
    public float tail_curve_offset;
    public float tail_side_offset;
    public float tail_bottom_padding;
    public float corner_radius;
    public float text_x_padding;
    public float text_y_padding;

    public BubbleLayoutConstants(float scale_factor) {
        // TODO: Remove this, scale factor ignored for now. 
        scale_factor = 2.0f;
        
        tail_width = 15.0f / scale_factor;
        tail_curve_offset = 2.5f / scale_factor;
        tail_side_offset = 0.0f / scale_factor;
        tail_bottom_padding = 4.0f / scale_factor;
        corner_radius = 34.0f / scale_factor;
        text_x_padding = 31.0f / scale_factor;
        text_y_padding = 8.0f / scale_factor;
    }
}

public abstract class BubbleLayout : Object, ChatItemLayout
{
    public bool from_me { get; set; }
    public float vertical_padding { get; set; }
    public string id { get; set; }

    protected float max_width;
    protected Widget parent;
    protected BubbleLayoutConstants constants;

    protected BubbleLayout(Widget parent, float max_width) {
        this.max_width = max_width;
        this.parent = parent;
        this.constants = BubbleLayoutConstants(parent.get_scale_factor());
    }

    private Gdk.RGBA background_color {
        get { 
            return from_me ? parent.get_color() : Gdk.RGBA() { 
                red = 1.0f, 
                green = 1.0f, 
                blue = 1.0f, 
                alpha = 0.08f 
            };
        }
    }

    public abstract float get_height();

    public abstract float get_width();
    
    public void draw(Snapshot snapshot) {
        with_bubble_clip(snapshot, (snapshot) => {
            draw_background(snapshot);
            draw_content(snapshot);
        });
    }

    public abstract void draw_content(Snapshot snapshot);

    public abstract void copy(Gdk.Clipboard clipboard);

    private void draw_background(Snapshot snapshot) 
    {
        var width = get_width();
        var height = get_height();
        var color = background_color;
        var bounds = Graphene.Rect().init(0, 0, width, height);

        const float gradient_darkening = 0.67f;
        var start_point = Graphene.Point() { x = 0, y = 0 };
        var end_point = Graphene.Point() { x = 0, y = height };
        var stops = new Gsk.ColorStop[] {
            Gsk.ColorStop() { offset = 0.0f, color = color },
            Gsk.ColorStop() { offset = 1.0f, color = Gdk.RGBA () { 
                red = color.red * gradient_darkening, 
                green = color.green * gradient_darkening, 
                blue = color.blue * gradient_darkening, 
                alpha = color.alpha 
            } },
        };
        
        snapshot.append_linear_gradient(bounds, start_point, end_point, stops);
    }

    protected void with_bubble_clip(Snapshot snapshot, Func<Snapshot> func) {
        var width = get_width();
        var height = get_height();

        if (width > 10 && height > 10) {
            var path = create_bubble_path(width, height, from_me);
            snapshot.push_fill(path, Gsk.FillRule.WINDING);
            func(snapshot);
            snapshot.pop();
        } else {
            func(snapshot);
        }
    }

    private Gsk.Path create_bubble_path(float width, float height, bool tail_on_right = false) 
    {
        var builder = new Gsk.PathBuilder();
    
        float bubble_width = width - constants.tail_width;
        float bubble_height = height - constants.tail_bottom_padding;
        
        // Base position adjustments based on tail position
        float x = tail_on_right ? 0.0f : constants.tail_width;
        float y = 0.0f;
        
        // Calculate tail direction multiplier (-1 for left, 1 for right)
        float dir = tail_on_right ? 1.0f : -1.0f;
        
        // Calculate tail side positions based on direction
        float tail_side_x = tail_on_right ? (x + bubble_width) : x;
        
        // Start at top corner opposite to the tail
        builder.move_to(tail_on_right ? (x + constants.corner_radius) : (x + bubble_width - constants.corner_radius), y);
        
        // Top edge
        builder.line_to(tail_on_right ? (x + bubble_width - constants.corner_radius) : (x + constants.corner_radius), y);
        
        // Top corner on tail side
        if (tail_on_right) {
            builder.html_arc_to(x + bubble_width, y, 
                          x + bubble_width, y + constants.corner_radius, 
                          constants.corner_radius);
        } else {
            builder.html_arc_to(x, y, 
                          x, y + constants.corner_radius, 
                          constants.corner_radius);
        }
        
        // Side edge on tail side
        builder.line_to(tail_side_x, y + bubble_height - constants.corner_radius);
        
        // Corner with tail
        float tail_tip_x = tail_side_x + (dir * (constants.tail_width - constants.tail_curve_offset));
        float tail_tip_y = y + bubble_height;
        
        // Control points for the bezier curve
        float ctrl_point1_x = tail_side_x + (dir * constants.tail_side_offset);
        float ctrl_point1_y = y + bubble_height - constants.corner_radius/3;
        
        float ctrl_point2_x = tail_side_x + (dir * (constants.tail_width / 2.0f));
        float ctrl_point2_y = y + bubble_height - 2;
        
        // Point where the tail meets the bottom edge
        float tail_base_x = tail_side_x - (dir * constants.corner_radius/2);
        float tail_base_y = y + bubble_height;
        
        // Draw the corner with tail using bezier curves
        builder.cubic_to(ctrl_point1_x, ctrl_point1_y, 
                        ctrl_point2_x, ctrl_point2_y, 
                        tail_tip_x, tail_tip_y);
        
        builder.cubic_to(tail_tip_x - (dir * constants.tail_curve_offset), tail_tip_y + constants.tail_curve_offset, 
                        tail_base_x + (dir * constants.tail_width), tail_base_y, 
                        tail_base_x, tail_base_y);
        
        // Bottom edge
        builder.line_to(tail_on_right ? (x + constants.corner_radius) : (x + bubble_width - constants.corner_radius), y + bubble_height);
        
        // Bottom corner opposite to tail
        if (tail_on_right) {
            builder.html_arc_to(x, y + bubble_height, 
                          x, y + bubble_height - constants.corner_radius, 
                          constants.corner_radius);
        } else {
            builder.html_arc_to(x + bubble_width, y + bubble_height, 
                          x + bubble_width, y + bubble_height - constants.corner_radius, 
                          constants.corner_radius);
        }
        
        // Side edge opposite to tail
        if (tail_on_right) {
            builder.line_to(x, y + constants.corner_radius);
            
            // Top corner to close path
            builder.html_arc_to(x, y, 
                          x + constants.corner_radius, y, 
                          constants.corner_radius);
        } else {
            builder.line_to(x + bubble_width, y + constants.corner_radius);
            
            // Top corner to close path
            builder.html_arc_to(x + bubble_width, y, 
                          x + bubble_width - constants.corner_radius, y, 
                          constants.corner_radius);
        }
        
        // Close the path
        builder.close();
    
        return builder.to_path();
    }
}


