package com.timgroup.tucker.info;

import java.net.URI;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

import static java.util.Objects.requireNonNull;

public abstract class Component {
    private final String id;
    private final String label;

    public Component(String id, String label) {
        this.id = requireNonNull(id);
        this.label = requireNonNull(label);
    }

    public final String getId() {
        return id;
    }
    
    public final String getLabel() {
        return label;
    }

    public abstract Report getReport();

    public final Component mapReport(UnaryOperator<Report> operator) {
        requireNonNull(operator);
        return new Component(id, label) {
            @Override
            public Report getReport() {
                return operator.apply(Component.this.getReport());
            }

            @Override
            public String toString() {
                return Component.this.toString();
            }
        };
    }

    public final Component withRunbook(Runbook runbook) {
        requireNonNull(runbook);
        return new Component(id, label) {
            @Override
            public Report getReport() {
                try {
                    Report underlyingReport = Component.this.getReport();
                    if (underlyingReport.hasRunbook()) {
                        return underlyingReport;
                    }
                    else {
                        return underlyingReport.withRunbook(runbook);
                    }
                } catch (Throwable t) {
                    return new Report(t, runbook);
                }
            }

            @Override
            public String toString() {
                return Component.this.toString();
            }
        };
    }

    public final Component withRunbook(URI runbookUri) {
        return withRunbook(new Runbook(runbookUri.toString()));
    }

    public final Component mapValue(UnaryOperator<Object> operator) {
        requireNonNull(operator);
        return mapReport(r -> r.mapValue(operator));
    }

    public final Component mapStatus(UnaryOperator<Status> operator) {
        requireNonNull(operator);
        return mapReport(r -> r.mapStatus(operator));
    }

    public final Component withStatusNoWorseThan(Status notWorse) {
        requireNonNull(notWorse);
        return mapReport(r -> r.withStatusNoWorseThan(notWorse));
    }

    public static Component supplyReport(String id, String label, Supplier<Report> supplier) {
        requireNonNull(supplier);
        return new Component(id, label) {
            @Override
            public Report getReport() {
                return supplier.get();
            }

            @Override
            public String toString() {
                return getId() + ":" + supplier;
            }
        };
    }

    public static Component supplyInfo(String id, String label, Supplier<String> supplier) {
        requireNonNull(supplier);
        return new Component(id, label) {
            @Override
            public Report getReport() {
                return new Report(Status.INFO, supplier.get());
            }

            @Override
            public String toString() {
                return getId() + ":info:" + supplier;
            }
        };
    }

    public static Component combine(String id, String label, BinaryOperator<Object> operator, Component... components) {
        if (components.length == 0) throw new IllegalArgumentException("components must not be empty");
        return supplyReport(id, label, () -> {
            Report[] reports = new Report[components.length];
            for (int i = 0; i < components.length; i++) {
                reports[i] = components[i].getReport();
            }
            return Report.combine(operator, reports);
        });
    }

    public static Component combineStringValues(String id, String label, BinaryOperator<String> operator, Component... components) {
        return combine(id, label, (v1, v2) -> operator.apply(String.valueOf(v1), String.valueOf(v2)), components);
    }

    public static Component joinStringValues(String id, String label, String separator, Component... components) {
        return combineStringValues(id, label, (s1, s2) -> String.join(separator, s1, s2), components);
    }

    public static Component joinStringValues(String id, String label, Component... components) {
        return joinStringValues(id, label, "\n", components);
    }
}

@@ -76,16 +76,54 @@
 tional;%0A
+import java.util.function.BiFunction;%0A
 import j
@@ -1152,35 +1152,92 @@
 ent 
-withRunbook(Runbook runbook
+mapReportHandlingError(BiFunction%3C? super Report, ? super Throwable, Report%3E handler
 ) %7B%0A
@@ -1259,23 +1259,23 @@
 NonNull(
-runbook
+handler
 );%0A     
@@ -1365,32 +1365,63 @@
 t getReport() %7B%0A
+                Report report;%0A
                 
@@ -1450,26 +1450,9 @@
     
-Report underlyingR
+r
 epor
@@ -1503,53 +1503,32 @@
     
-    if (underlyingReport.hasRunbook()) %7B%0A    
+%7D catch (Throwable t) %7B%0A
     
@@ -1554,30 +1554,32 @@
 urn 
-u
+ha
 nd
+l
 er
-lyingReport;%0A    
+.apply(null, t);%0A
     
@@ -1596,43 +1596,8 @@
   %7D%0A
-                    else %7B%0A        
     
@@ -1619,44 +1619,34 @@
 urn 
-u
+ha
 nd
+l
 er
-lyingReport.withRunbook(runbook
+.apply(report, null
 );%0A 
@@ -1652,32 +1652,35 @@
             
+%7D%0A%0A
         
 %7D%0A          
@@ -1659,38 +1659,46 @@
      %7D%0A%0A        
-%7D%0A
     
+@Override%0A
             %7D ca
@@ -1697,80 +1697,82 @@
     
-%7D catch (Throwable t) %7B%0A                    return new Report(t, runbook
+public String toString() %7B%0A                return Component.this.toString(
 );%0A 
@@ -1778,34 +1778,26 @@
             
-    %7D%0A    
+%7D%0A
         %7D%0A%0A 
@@ -1797,144 +1797,237 @@
    %7D
-%0A
+;
 %0A    
-        @Override%0A            public String toString() %7B%0A                return Component.this.toString();%0A            %7D%0A        %7D
+%7D%0A%0A    public final Component withRunbook(Runbook runbook) %7B%0A        requireNonNull(runbook);%0A        return mapReportHandlingError((r, t) -%3E r != null ? r.hasRunbook() ? r : r.withRunbook(runbook) : new Report(t, runbook))
 ;%0A  

Support other users handling report-generation errors too