# data[].attributes.body
https://docs.google.com/document/d/1zYSibCyebrh6-BoXd6-EUIVPOgiWDR4NCEnLL2WdNtg/edit?tab=t.0

# data[].attributes.body
🌿 Survey Questionnaire: Smart Plant Care Device – Bloomer

Section A: Demographics (Optional)

1. Age group:
☐ Under 18
☐ 18–24
☐ 25–34
☐ 35–44
☐ 45–60
☐ Above 60

2. What is your current living environment?
☐ Apartment / Condominium
☐ Terrace / Link House
☐ Bungalow / Semi-D
☐ Hostel / Dormitory
☐ Others: ___________

Section B: Gardening Habits and Interests

3. Do you currently own any houseplants?
☐ Yes, many
☐ Yes, a few
☐ Not now, but planning to
☐ No

4. How confident are you in taking care of plants?
☐ Very confident
☐ Somewhat confident
☐ Not confident
☐ I have no experience

5. What are your main challenges in plant care? (Select all that apply)
☐ Forgetting to water
☐ Unsure how much sunlight is needed
☐ Difficulty understanding soil or plant health
☐ Lack of time
☐ Others: ___________

Section C: Product Interest – Bloomer

6. Would you be interested in a smart device that monitors your plant’s health and gives care tips through a mobile app?
☐ Very interested
☐ Somewhat interested
☐ Not interested
☐ Unsure

7. Which features would you find most useful in such a device? (Select up to 3)
☐ Soil moisture detection
☐ Light and temperature monitoring
☐ Soil pH and nutrient analysis
☐ Mobile app alerts and tips
☐ AI-based personalized care plans
☐ Smart home integration (e.g., Google Home)

8. What is the maximum price you are willing to pay for such a smart plant care device?
☐ Below RM50
☐ RM51 – RM100
☐ RM101 – RM150
☐ RM151 – RM200
☐ Above RM200

9. Would you prefer a one-time purchase or a device with a subscription service for advanced features (e.g., AI tips, cloud sync)?
☐ One-time purchase only
☐ I’m open to monthly/yearly subscription
☐ Depends on the price and features

10. Would you be willing to participate in early product testing or provide feedback before the product launches?
☐ Yes
☐ Maybe
☐ No

# data[].attributes.body
Report

https://docs.google.com/document/d/1p2SEgdLF1tzg6vNMAzckhomneNJi2-yPVgzmKhY-Q_E/edit?usp=sharing

Slide

https://www.canva.com/design/DAGl1kqCisg/5iye9nMVAi7vSlJzU0V8PA/edit?utm_content=DAGl1kqCisg&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton

# data[].attributes.body
executive summary - pijul

problem statement - izzul

technology solution - hefi

implementation plan - alep

conclusion and future outlook - piju

# data[].attributes.attachment_link.title
Cream Colorful Pastel Cute Illustration Project Presentation

# data[].attributes.body
Executive Summary (1 page)

• A concise summary that provides an overview of the business problem, the proposed

technology solution, and the expected outcomes. This section is meant to capture

the main points of the proposal for stakeholders who may not have time to read the

entire document.

Problem Statement (1 page)

• Clearly define the business problem that the proposed technology solution will

address.

• Justify why this problem is important and relevant to the target industry or

organization.

Technology Solution (4 pages)

• A detailed explanation of the specialized technology being proposed (e.g., AI,

machine learning, blockchain, etc.).

• Describe how the technology works and why it is appropriate for solving the identified

problem.

• Include research or case studies to support the choice of technology.

Feasability Study , Business Impact.

Hardware Architecture.

Implementation Plan (4 pages)

• A step-by-step plan outlining the implementation of the proposed technology

solution.

• Include timelines, required resources, and stakeholders involved in the process.

• Identify potential risks and challenges, along with mitigation strategies.

• Provide cost estimates and potential ROI (return on investment).

Conclusion and Future Outlook (1 page)

• Summarize the expected impact of the technology solution on the business problem.

• Discuss any future developments or enhancements that could improve the solution.

# data[].attributes.attachment_link.readable_text
import streamlit as st
import pandas as pd
import numpy as np
import time
from algorithms import ClosestPairSolver
from visualization import OutletVisualizer
from performance_analysis import PerformanceAnalyzer


def main():
    st.set_page_config(page_title="CheckGPT Outlet Closure Analysis",
                       page_icon="����",
                       layout="wide")

    st.title("���� CheckGPT Outlet Closure Analysis System")
    st.markdown(
        "**CSC645 Group Assignment - Closest Pair Problem Implementation**")

    # Sidebar for navigation
    st.sidebar.title("Navigation")
    page = st.sidebar.selectbox("Choose a section:", [
        "Problem Overview", "Algorithm Implementation", "Interactive Analysis",
        "Performance Testing", "Java Code Reference"
    ])

    if page == "Problem Overview":
        show_problem_overview()
    elif page == "Algorithm Implementation":
        show_algorithm_implementation()
    elif page == "Interactive Analysis":
        show_interactive_analysis()
    elif page == "Performance Testing":
        show_performance_testing()
    elif page == "Java Code Reference":
        show_java_reference()


def show_problem_overview():
    st.header("Problem Overview")

    st.markdown("""
    ### CheckGPT Outlet Closure Problem
    
    **Context:** CheckGPT Sdn. Bhd. operates 20 outlets throughout Malaysia and wants to reduce operating costs 
    by closing outlets that are closest to each other.
    
    **Objective:** Find pairs of outlets that are closest to each other to determine which ones should be closed.
    
    **Mathematical Formulation:** Given n points (outlets) in a 2D coordinate system, find the pair of points 
    with the minimum Euclidean distance.
    """)

    # Show example data
    st.subheader("Example Outlets from Assignment")
    example_data = {
        'Outlet': ['A', 'B', 'C', 'D', 'E'],
        'Coordinate X': [1, 3, 1, 4, 5],
        'Coordinate Y': [3, 3, 1, 2, 2]
    }
    df = pd.DataFrame(example_data)
    st.dataframe(df)

    # Visualize example
    visualizer = OutletVisualizer()
    fig = visualizer.plot_outlets(df['Coordinate X'].tolist(),
                                  df['Coordinate Y'].tolist(),
                                  df['Outlet'].tolist())
    st.plotly_chart(fig, use_container_width=True)

    # Calculate and show closest pair for example
    solver = ClosestPairSolver()
    points = list(zip(df['Coordinate X'], df['Coordinate Y']))
    closest_pair, min_distance = solver.brute_force_closest_pair(points)

    st.success(
        f"**Closest Pair:** Outlets at positions {closest_pair[0]} and {closest_pair[1]} with distance: {min_distance:.2f}"
    )

    # Add 20 outlets demonstration section
    st.subheader("Assignment Scenario: 20 Outlets Analysis")

    st.markdown("""
    **Scenario:** CheckGPT Sdn. Bhd. has 20 outlets across Malaysia and needs to identify 
    the 10 closest pairs to determine which outlets should be closed to reduce operating costs.
    """)

    if st.button("Generate 20 Sample Outlets & Find Closest Pairs"):
        with st.spinner("Generating outlets and analyzing closest pairs..."):
            # Start with the given sample outlets and generate 15 more
            np.random.seed(123)

            # Sample outlets from assignment (A, B, C, D, E)
            sample_outlets = [('A', 1, 3), ('B', 3, 3), ('C', 1, 1),
                              ('D', 4, 2), ('E', 5, 2)]

            # Generate 15 additional outlets with strategic placement for desired distances
            additional_outlets = []
            used_coords = {(x, y) for _, x, y in sample_outlets}

            # Generate outlet names F through T (15 more outlets)
            outlet_letters = [chr(ord('F') + i) for i in range(15)]

            # Create outlets strategically to ensure we get 10 unique closest pairs
            strategic_coords = [
                (2, 3),  # F: Close to A(1,3) - distance 1.0
                (6, 2),  # G: Close to E(5,2) - distance 1.0  
                (2, 1),  # H: Close to C(1,1) - distance 1.0
                (4, 3),  # I: Close to B(3,3) - distance 1.0
                (5, 1),  # J: Close to D(4,2) - distance 1.414
                (7, 2),  # K: Forms pair with L
                (7, 1),  # L: Close to K(7,2) - distance 1.0
                (1, 4),  # M: Forms pair with N
                (2, 4),  # N: Close to M(1,4) - distance 1.0
                (9, 1),  # O: Forms pair with P
                (10, 1),  # P: Close to O(9,1) - distance 1.0
                (3, 5),  # Q: Forms pair with R
                (4, 5),  # R: Close to Q(3,5) - distance 1.0
                (8, 3),  # S: Forms pair with T
                (9, 3),  # T: Close to S(8,3) - distance 1.0
            ]

            # Use strategic coordinates first, then random for remaining
            for i in range(15):
                if i < len(strategic_coords):
                    x, y = strategic_coords[i]
                    if (x, y) not in used_coords:
                        used_coords.add((x, y))
                        additional_outlets.append((outlet_letters[i], x, y))
                    else:
                        # If strategic coord is taken, find nearby alternative
                        for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1),
                                       (1, 1), (-1, -1)]:
                            alt_x, alt_y = x + dx, y + dy
                            if 1 <= alt_x <= 10 and 1 <= alt_y <= 10 and (
                                    alt_x, alt_y) not in used_coords:
                                used_coords.add((alt_x, alt_y))
                                additional_outlets.append(
                                    (outlet_letters[i], alt_x, alt_y))
                                break
                else:
                    # Random placement for remaining outlets
                    while True:
                        x = np.random.randint(1, 11)
                        y = np.random.randint(1, 11)
                        if (x, y) not in used_coords:
                            used_coords.add((x, y))
                            additional_outlets.append(
                                (outlet_letters[i], x, y))
                            break

            # Combine all outlets
            all_outlets = sample_outlets + additional_outlets

            outlet_data = {
                'name': [outlet[0] for outlet in all_outlets],
                'x': [float(outlet[1]) for outlet in all_outlets],
                'y': [float(outlet[2]) for outlet in all_outlets]
            }

            outlets_20 = list(zip(outlet_data['x'], outlet_data['y']))

            # Display the complete list of 20 outlets first
            st.markdown("**Complete List of 20 Outlets:**")
            outlets_display = []
            for i, (name, x, y) in enumerate(
                    zip(outlet_data['name'], outlet_data['x'],
                        outlet_data['y'])):
                outlets_display.append({
                    'Outlet': name,
                    'X Coordinate': int(x),
                    'Y Coordinate': int(y)
                })

            outlets_df = pd.DataFrame(outlets_display)
            st.dataframe(outlets_df, use_container_width=True, height=400)

            # Find all pairs and sort by distance
            all_pairs = []
            for i in range(19):
                for j in range(i + 1, 20):
                    distance = solver.euclidean_distance(
                        outlets_20[i], outlets_20[j])
                    all_pairs.append(
                        (outlets_20[i], outlets_20[j], distance,
                         outlet_data['name'][i], outlet_data['name'][j]))

            # Sort and show all pairs, then select for closure
            all_pairs.sort(key=lambda x: x[2])

            # Display ALL pairs first (not just closest)
            st.markdown("**All Possible Outlet Pairs (Ranked by Distance):**")
            pairs_table = []
            for i, (p1, p2, dist, name1,
                    name2) in enumerate(all_pairs, 1):  # Show ALL pairs
                pairs_table.append({
                    'Rank': i,
                    'Outlet Pair': f"{name1} - {name2}",
                    'Coordinates':
                    f"({int(p1[0])}, {int(p1[1])}) - ({int(p2[0])}, {int(p2[1])})",
                    'Distance': f"{dist:.3f}"
                })

            pairs_df = pd.DataFrame(pairs_table)
            st.dataframe(pairs_df, use_container_width=True, height=600)

            # Now select pairs for closure (ensuring 10 unique outlets)
            top_10_pairs = all_pairs[:10]  # Use top 10 for closure analysis

            # Visualization
            visualizer = OutletVisualizer()
            fig = visualizer.plot_multiple_close_pairs(
                outlet_data['x'],
                outlet_data['y'],
                outlet_data['name'],
                [(p1, p2, dist) for p1, p2, dist, _, _ in top_10_pairs[:5]],
                title="20 Outlets - Top 5 Closest Pairs Highlighted")
            st.plotly_chart(fig, use_container_width=True)

            # Summary statistics
            col1, col2, col3 = st.columns(3)
            with col1:
                st.metric("Total Outlets", "20")
            with col2:
                st.metric("Closest Pair Distance", f"{top_10_pairs[0][2]:.3f}")
            with col3:
                st.metric("Average of Top 10",
                          f"{sum(p[2] for p in top_10_pairs)/10:.3f}")

            # Determine which outlets to close - use greedy approach to get exactly 10
            outlets_to_close = []
            used_outlets = set()

            st.markdown("**Outlet Closure Analysis:**")

            closure_recommendations = []
            pair_rank = 1

            for p1, p2, dist, name1, name2 in all_pairs:
                # Skip if either outlet is already involved in a closure decision
                if name1 in used_outlets or name2 in used_outlets:
                    continue

                # Choose which outlet to close from this pair
                # Strategy: close the later alphabetical one to keep earlier letters
                if name1 > name2:
                    outlet_to_close = name1
                    outlet_to_keep = name2
                else:
                    outlet_to_close = name2
                    outlet_to_keep = name1

                outlets_to_close.append(outlet_to_close)
                used_outlets.add(name1)
                used_outlets.add(name2)

                closure_recommendations.append({
                    'Pair Used':
                    pair_rank,
                    'Pair':
                    f"{name1} - {name2}",
                    'Close Outlet':
                    outlet_to_close,
                    'Keep Outlet':
                    outlet_to_keep,
                    'Distance':
                    f"{dist:.3f}",
                    'Coordinates Closed':
                    f"({int(p1[0])}, {int(p1[1])})" if outlet_to_close == name1
                    else f"({int(p2[0])}, {int(p2[1])})"
                })

                pair_rank += 1

                # Stop when we have 10 closures
                if len(outlets_to_close) >= 10:
                    break

            closure_df = pd.DataFrame(closure_recommendations)
            st.dataframe(closure_df, use_container_width=True)

            # Summary of closures
            remaining_outlets = set(
                outlet_data['name']) - set(outlets_to_close)

            st.markdown("**���� Closure Summary:**")
            col1, col2, col3 = st.columns(3)
            with col1:
                st.metric("Outlets to Close", len(outlets_to_close))
            with col2:
                st.metric("Outlets Remaining", len(remaining_outlets))
            with col3:
                st.metric("Total Original", "20")

            # Show the list of outlets to close
            st.markdown("**���� Final List of Outlets to Close:**")
            outlets_to_close_display = sorted(outlets_to_close)
            st.code(f"Outlets to close: {', '.join(outlets_to_close_display)}")

            # Show remaining operational outlets
            st.markdown("**��� Remaining Operational Outlets:**")
            remaining_display = sorted(list(remaining_outlets))
            st.code(f"Operational outlets: {', '.join(remaining_display)}")

            st.info(
                "**Business Recommendation:** Close the outlets listed above to eliminate redundancy while maintaining geographic coverage with the remaining outlets."
            )

            # Export functionality
            export_data = pd.DataFrame([{
                'Outlet_1': p[3],
                'Outlet_2': p[4],
                'X1': int(p[0][0]),
                'Y1': int(p[0][1]),
                'X2': int(p[1][0]),
                'Y2': int(p[1][1]),
                'Distance': p[2],
                'Rank': i + 1
            } for i, p in enumerate(top_10_pairs)])

            csv_data = export_data.to_csv(index=False)
            st.download_button("Download 20 Outlets Analysis (CSV)", csv_data,
                               "20_outlets_closest_pairs.csv", "text/csv")


def show_algorithm_implementation():
    st.header("Algorithm Implementation")

    st.markdown("""
    ### Two Approaches Implemented:
    
    1. **Brute Force Approach** - O(n��) time complexity
    2. **Divide and Conquer Approach** - O(n log n) time complexity
    """)

    # Show algorithm pseudocode
    tab1, tab2 = st.tabs(
        ["Brute Force Algorithm", "Divide & Conquer Algorithm"])

    with tab1:
        st.markdown("""
        #### Brute Force Pseudocode:
        ```
        function bruteForceclosestPair(points):
            n = length(points)
            minDistance = INFINITY
            closestPair = null
            
            for i = 0 to n-2:
                for j = i+1 to n-1:
                    distance = euclideanDistance(points[i], points[j])
                    if distance < minDistance:
                        minDistance = distance
                        closestPair = (points[i], points[j])
            
            return closestPair, minDistance
        ```
        
        **Time Complexity:** O(n��)  
        **Space Complexity:** O(1)
        """)

    with tab2:
        st.markdown("""
        #### Divide & Conquer Pseudocode:
        ```
        function divideConquerClosestPair(points):
            if length(points) <= 3:
                return bruteForceclosestPair(points)
            
            mid = length(points) / 2
            leftPoints = points[0...mid]
            rightPoints = points[mid+1...end]
            
            leftPair, leftDistance = divideConquerClosestPair(leftPoints)
            rightPair, rightDistance = divideConquerClosestPair(rightPoints)
            
            minDistance = min(leftDistance, rightDistance)
            closestPair = leftPair if leftDistance < rightDistance else rightPair
            
            // Check points across the divide
            stripPair, stripDistance = findClosestInStrip(points, mid, minDistance)
            
            if stripDistance < minDistance:
                return stripPair, stripDistance
            else:
                return closestPair, minDistance
        ```
        
        **Time Complexity:** O(n log n)  
        **Space Complexity:** O(n)
        """)


def show_interactive_analysis():
    st.header("Interactive Outlet Analysis")

    # Input methods
    input_method = st.radio(
        "Choose input method:",
        ["Manual Entry", "Upload CSV", "Generate Random Data"])

    points = []
    labels = []

    if input_method == "Manual Entry":
        st.subheader("Enter Outlet Coordinates")

        # Use session state to maintain data
        if 'outlets' not in st.session_state:
            st.session_state.outlets = []

        col1, col2, col3 = st.columns(3)
        with col1:
            outlet_name = st.text_input(
                "Outlet Name",
                value=f"Outlet_{len(st.session_state.outlets)+1}")
        with col2:
            x_coord = st.number_input("X Coordinate", value=0.0)
        with col3:
            y_coord = st.number_input("Y Coordinate", value=0.0)

        if st.button("Add Outlet"):
            st.session_state.outlets.append({
                'name': outlet_name,
                'x': x_coord,
                'y': y_coord
            })
            st.rerun()

        if st.session_state.outlets:
            st.subheader("Current Outlets")
            df = pd.DataFrame(st.session_state.outlets)
            st.dataframe(df)

            if st.button("Clear All Outlets"):
                st.session_state.outlets = []
                st.rerun()

            points = [(outlet['x'], outlet['y'])
                      for outlet in st.session_state.outlets]
            labels = [outlet['name'] for outlet in st.session_state.outlets]

    elif input_method == "Upload CSV":
        st.subheader("Upload CSV File")
        st.markdown("CSV should have columns: name, x, y")

        uploaded_file = st.file_uploader("Choose CSV file", type="csv")
        if uploaded_file:
            df = pd.read_csv(uploaded_file)
            st.dataframe(df)

            if all(col in df.columns for col in ['name', 'x', 'y']):
                points = list(zip(df['x'], df['y']))
                labels = df['name'].tolist()
            else:
                st.error("CSV must contain columns: name, x, y")

    elif input_method == "Generate Random Data":
        st.subheader("Generate Random Outlets")

        n_outlets = st.slider("Number of outlets", 5, 50,
                              20)  # Default to 20 as requested
        x_range = st.slider("X coordinate range", 1, 20, (1, 10))
        y_range = st.slider("Y coordinate range", 1, 20, (1, 10))

        if st.button("Generate Random Data"):
            np.random.seed(42)  # For reproducibility
            x_coords = np.random.randint(x_range[0], x_range[1], n_outlets)
            y_coords = np.random.randint(y_range[0], y_range[1], n_outlets)

            points = list(zip(x_coords.astype(float), y_coords.astype(float)))
            # Use alphabetical naming for outlets
            labels = [chr(ord('A') + i) for i in range(min(n_outlets, 26))]
            if n_outlets > 26:
                # For more than 26 outlets, use AA, AB, AC... format
                for i in range(26, n_outlets):
                    first_letter = chr(ord('A') + (i - 26) // 26)
                    second_letter = chr(ord('A') + (i - 26) % 26)
                    labels.append(f"{first_letter}{second_letter}")

    # Analysis section
    if len(points) >= 2:
        st.subheader("Analysis Results")

        solver = ClosestPairSolver()

        # Algorithm selection
        algorithm = st.selectbox("Choose algorithm:", [
            "Brute Force", "Divide & Conquer", "Both (Compare)",
            "Find Multiple Close Pairs"
        ])

        # Add threshold input for multiple close pairs
        threshold = 5.0
        num_pairs_to_show = 10

        if algorithm == "Find Multiple Close Pairs":
            threshold = st.number_input(
                "Distance threshold for close pairs:",
                min_value=0.1,
                max_value=50.0,
                value=5.0,
                step=0.5,
                help="Find all pairs within this distance")
            max_pairs = len(points) * (len(points) -
                                       1) // 2 if len(points) > 0 else 10
            num_pairs_to_show = st.slider("Number of closest pairs to show:",
                                          min_value=1,
                                          max_value=min(20, max_pairs),
                                          value=10,
                                          help="Show the top N closest pairs")

        if st.button("Run Analysis"):
            with st.spinner("Analyzing outlets..."):

                bf_pair = None
                dc_pair = None

                if algorithm == "Brute Force" or algorithm == "Both (Compare)":
                    start_time = time.time()
                    bf_pair, bf_distance = solver.brute_force_closest_pair(
                        points)
                    bf_time = time.time() - start_time

                    st.success(f"**Brute Force Result:**")
                    st.write(f"Closest pair: {bf_pair}")
                    st.write(f"Distance: {bf_distance:.4f}")
                    st.write(f"Execution time: {bf_time:.6f} seconds")

                if algorithm == "Divide & Conquer" or algorithm == "Both (Compare)":
                    start_time = time.time()
                    dc_pair, dc_distance = solver.divide_conquer_closest_pair(
                        points)
                    dc_time = time.time() - start_time

                    st.success(f"**Divide & Conquer Result:**")
                    st.write(f"Closest pair: {dc_pair}")
                    st.write(f"Distance: {dc_distance:.4f}")
                    st.write(f"Execution time: {dc_time:.6f} seconds")

                if algorithm == "Find Multiple Close Pairs":
                    st.success(
                        f"**Finding {num_pairs_to_show} Closest Pairs:**")

                    # Get all pairs sorted by distance
                    all_pairs = []
                    n = len(points)

                    for i in range(n - 1):
                        for j in range(i + 1, n):
                            distance = solver.euclidean_distance(
                                points[i], points[j])
                            all_pairs.append((points[i], points[j], distance))

                    # Sort by distance and take top N
                    all_pairs.sort(key=lambda x: x[2])
                    top_pairs = all_pairs[:num_pairs_to_show]

                    # Display results in a table
                    pairs_data = []
                    for i, (p1, p2, dist) in enumerate(top_pairs, 1):
                        # Find outlet names
                        p1_idx = points.index(p1)
                        p2_idx = points.index(p2)
                        p1_name = labels[p1_idx] if p1_idx < len(
                            labels) else chr(ord('A') + p1_idx)
                        p2_name = labels[p2_idx] if p2_idx < len(
                            labels) else chr(ord('A') + p2_idx)

                        pairs_data.append({
                            'Rank': i,
                            'Outlet Pair': f"{p1_name} - {p2_name}",
                            'First Outlet': p1_name,
                            'First Coordinates':
                            f"({int(p1[0])}, {int(p1[1])})",
                            'Second Outlet': p2_name,
                            'Second Coordinates':
                            f"({int(p2[0])}, {int(p2[1])})",
                            'Distance': f"{dist:.4f}"
                        })

                    pairs_df = pd.DataFrame(pairs_data)
                    st.dataframe(pairs_df, use_container_width=True)

                    # Show recommendation
                    st.info(
                        f"**Recommendation:** Based on the closest pairs analysis, consider closing outlets that are within {threshold:.1f} distance units of each other to reduce operating costs."
                    )

                    # Visualization with multiple pairs
                    visualizer = OutletVisualizer()
                    x_coords = [p[0] for p in points]
                    y_coords = [p[1] for p in points]

                    # Show top 5 pairs in visualization for clarity
                    display_pairs = top_pairs[:5]
                    fig = visualizer.plot_multiple_close_pairs(
                        x_coords,
                        y_coords,
                        labels,
                        display_pairs,
                        title=f"Top {len(display_pairs)} Closest Outlet Pairs")
                    st.plotly_chart(fig, use_container_width=True)

                    # Export data for report
                    csv = pairs_df.to_csv(index=False)
                    st.download_button(
                        "Download Closest Pairs Data (CSV)", csv,
                        f"closest_pairs_top_{num_pairs_to_show}.csv",
                        "text/csv")

                else:
                    # Single closest pair visualization
                    visualizer = OutletVisualizer()
                    x_coords = [p[0] for p in points]
                    y_coords = [p[1] for p in points]

                    # Get the closest pair for highlighting
                    if algorithm == "Divide & Conquer" and dc_pair:
                        result_pair = dc_pair
                    elif bf_pair:
                        result_pair = bf_pair
                    else:
                        # Fallback: calculate closest pair
                        result_pair, _ = solver.brute_force_closest_pair(
                            points)

                    if result_pair:
                        fig = visualizer.plot_outlets_with_closest_pair(
                            x_coords, y_coords, labels, result_pair)
                    else:
                        # Fallback visualization without highlighting
                        fig = visualizer.plot_outlets(x_coords, y_coords,
                                                      labels)

                    st.plotly_chart(fig, use_container_width=True)
                    st.plotly_chart(fig, use_container_width=True)


def show_performance_testing():
    st.header("Performance Testing & Time Complexity Analysis")

    st.markdown("""
    This section provides experimental data for time complexity analysis required for your assignment report.
    
    **Important for Report:** Use this data to create graphs and analyze the time complexity of both algorithms.
    """)

    analyzer = PerformanceAnalyzer()

    # Test configuration
    st.subheader("Test Configuration")
    col1, col2 = st.columns(2)

    with col1:
        min_size = st.number_input("Minimum dataset size", 5, 100, 10)
        max_size = st.number_input("Maximum dataset size", 50, 1000, 100)

    with col2:
        step_size = st.number_input("Step size", 5, 50, 10)
        num_trials = st.number_input("Number of trials per size", 1, 10, 3)

    if st.button("Run Performance Analysis"):
        with st.spinner(
                "Running performance tests... This may take a while for large datasets."
        ):

            sizes = list(range(min_size, max_size + 1, step_size))
            results = analyzer.compare_algorithms(sizes, num_trials)

            # Display results table
            st.subheader("Performance Results")
            df_results = pd.DataFrame(results)
            st.dataframe(df_results)

            # Plot results
            fig = analyzer.plot_performance_comparison(results)
            st.plotly_chart(fig, use_container_width=True)

            # Statistical analysis
            st.subheader("Statistical Analysis")

            if len(results) > 1:
                # Calculate growth rates
                bf_times = [r['brute_force_time'] for r in results]
                dc_times = [r['divide_conquer_time'] for r in results]

                st.markdown("**Time Complexity Observations:**")
                st.write(
                    f"- Brute Force: Shows quadratic growth pattern O(n��)")
                st.write(
                    f"- Divide & Conquer: Shows linearithmic growth pattern O(n log n)"
                )

                # Performance ratio
                if max(dc_times) > 0:
                    max_ratio = bf_times[-1] / dc_times[-1]
                    st.write(
                        f"- At n={sizes[-1]}, Brute Force is ~{max_ratio:.1f}x slower than Divide & Conquer"
                    )

            # Export data for report
            st.subheader("Export Data for Report")
            csv = df_results.to_csv(index=False)
            st.download_button("Download Performance Data (CSV)", csv,
                               "performance_analysis.csv", "text/csv")


def show_java_reference():
    st.header("Java Code Reference")

    st.markdown("""
    This section provides Java code templates that you can use for your assignment implementation.
    The logic directly mirrors the Python implementation above.
    """)

    tab1, tab2, tab3 = st.tabs(
        ["Main Class", "Brute Force Algorithm", "Divide & Conquer Algorithm"])

    with tab1:
        st.markdown("#### Main Java Class")
        st.code("""
public class OutletClosureAnalysis {
    
    public static class Point {
        public double x, y;
        public String name;
        
        public Point(double x, double y, String name) {
            this.x = x;
            this.y = y;
            this.name = name;
        }
        
        public double distanceTo(Point other) {
            return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
        }
        
        @Override
        public String toString() {
            return String.format("%s(%.2f, %.2f)", name, x, y);
        }
    }
    
    public static class ClosestPairResult {
        public Point point1, point2;
        public double distance;
        
        public ClosestPairResult(Point p1, Point p2, double dist) {
            this.point1 = p1;
            this.point2 = p2;
            this.distance = dist;
        }
    }
    
    public static void main(String[] args) {
        // Example usage
        Point[] outlets = {
            new Point(1, 3, "A"),
            new Point(3, 3, "B"),
            new Point(1, 1, "C"),
            new Point(4, 2, "D"),
            new Point(5, 2, "E")
        };
        
        // Test both algorithms
        ClosestPairResult bruteForceResult = bruteForceClosestPair(outlets);
        ClosestPairResult divideConquerResult = divideConquerClosestPair(outlets);
        
        System.out.println("Brute Force Result: " + bruteForceResult.point1 + 
                          " and " + bruteForceResult.point2 + 
                          " with distance " + bruteForceResult.distance);
    }
    
    // Algorithm implementations go here...
}
        """,
                language="java")

    with tab2:
        st.markdown("#### Brute Force Algorithm Implementation")
        st.code("""
public static ClosestPairResult bruteForceClosestPair(Point[] points) {
    int n = points.length;
    if (n < 2) {
        throw new IllegalArgumentException("Need at least 2 points");
    }
    
    double minDistance = Double.MAX_VALUE;
    Point closestPoint1 = null, closestPoint2 = null;
    
    // Compare all pairs of points
    for (int i = 0; i < n - 1; i++) {
        for (int j = i + 1; j < n; j++) {
            double distance = points[i].distanceTo(points[j]);
            if (distance < minDistance) {
                minDistance = distance;
                closestPoint1 = points[i];
                closestPoint2 = points[j];
            }
        }
    }
    
    return new ClosestPairResult(closestPoint1, closestPoint2, minDistance);
}

// Time Complexity: O(n��)
// Space Complexity: O(1)
        """,
                language="java")

    with tab3:
        st.markdown("#### Divide & Conquer Algorithm Implementation")
        st.code("""
public static ClosestPairResult divideConquerClosestPair(Point[] points) {
    // Sort points by x-coordinate
    Point[] sortedPoints = points.clone();
    Arrays.sort(sortedPoints, (a, b) -> Double.compare(a.x, b.x));
    
    return divideConquerHelper(sortedPoints);
}

private static ClosestPairResult divideConquerHelper(Point[] points) {
    int n = points.length;
    
    // Base case: use brute force for small arrays
    if (n <= 3) {
        return bruteForceClosestPair(points);
    }
    
    // Divide
    int mid = n / 2;
    Point[] leftPoints = Arrays.copyOfRange(points, 0, mid);
    Point[] rightPoints = Arrays.copyOfRange(points, mid, n);
    
    // Conquer
    ClosestPairResult leftResult = divideConquerHelper(leftPoints);
    ClosestPairResult rightResult = divideConquerHelper(rightPoints);
    
    // Find minimum of the two results
    ClosestPairResult minResult = (leftResult.distance <= rightResult.distance) ? 
                                  leftResult : rightResult;
    
    // Check points across the dividing line
    double midX = points[mid].x;
    ClosestPairResult stripResult = findClosestInStrip(points, midX, minResult.distance);
    
    return (stripResult.distance < minResult.distance) ? stripResult : minResult;
}

private static ClosestPairResult findClosestInStrip(Point[] points, double midX, double minDist) {
    // Create array of points close to the dividing line
    List<Point> strip = new ArrayList<>();
    for (Point p : points) {
        if (Math.abs(p.x - midX) < minDist) {
            strip.add(p);
        }
    }
    
    // Sort by y-coordinate
    strip.sort((a, b) -> Double.compare(a.y, b.y));
    
    double minDistance = minDist;
    Point closestPoint1 = null, closestPoint2 = null;
    
    // Check each point with next 7 points (optimization)
    for (int i = 0; i < strip.size(); i++) {
        for (int j = i + 1; j < strip.size() && 
             (strip.get(j).y - strip.get(i).y) < minDistance; j++) {
            double distance = strip.get(i).distanceTo(strip.get(j));
            if (distance < minDistance) {
                minDistance = distance;
                closestPoint1 = strip.get(i);
                closestPoint2 = strip.get(j);
            }
        }
    }
    
    return new ClosestPairResult(closestPoint1, closestPoint2, minDistance);
}

// Time Complexity: O(n log n)
// Space Complexity: O(n)
        """,
                language="java")

    st.markdown("""
    ### Performance Testing Code for Java
    
    Add this method to measure execution time for your complexity analysis:
    """)

    st.code("""
public static void performanceTest() {
    int[] testSizes = {10, 20, 50, 100, 200, 500};
    
    System.out.println("Size\\tBrute Force (ms)\\tDivide & Conquer (ms)");
    
    for (int size : testSizes) {
        // Generate random points
        Point[] points = generateRandomPoints(size);
        
        // Test Brute Force
        long startTime = System.nanoTime();
        bruteForceClosestPair(points);
        long bruteForceTime = System.nanoTime() - startTime;
        
        // Test Divide & Conquer
        startTime = System.nanoTime();
        divideConquerClosestPair(points);
        long divideConquerTime = System.nanoTime() - startTime;
        
        System.out.printf("%d\\t%.3f\\t\\t%.3f%n", 
                         size, 
                         bruteForceTime / 1_000_000.0, 
                         divideConquerTime / 1_000_000.0);
    }
}

private static Point[] generateRandomPoints(int n) {
    Random random = new Random(42); // Fixed seed for reproducibility
    Point[] points = new Point[n];
    
    for (int i = 0; i < n; i++) {
        double x = random.nextDouble() * 100;
        double y = random.nextDouble() * 100;
        points[i] = new Point(x, y, "P" + i);
    }
    
    return points;
}
    """,
            language="java")


if __name__ == "__main__":
    main()

# data[].attributes.body
https://docs.google.com/document/d/1VkGddX9PvyMb9NxfX-HECObb8738vK7hl6H3EOBtmJA/edit?usp=sharing