
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Test {

    static class DataPoint {
        private long   timestamp;
        private Object value;

        public DataPoint(long timestamp, Object value) {
            this.timestamp = timestamp;
            this.value = value;
        }

        public long getTimestamp() {
            return timestamp;
        }

        public Object getValue() {
            return value;
        }

        public String toString() {
            return timestamp + ": " + value;
        }
    }

    static class NamedKeeparator implements Iterator<DataPoint> {
        private final Iterator<DataPoint> delegate;
        private final String name;
        private DataPoint                 current;

        public NamedKeeparator(String name, Iterator<DataPoint> delegate) {
            this.delegate = delegate;
            this.name = name;
        }

        @Override
        public boolean hasNext() {
            return delegate.hasNext();
        }

        @Override
        public DataPoint next() {
            return current = delegate.next();
        }

        public DataPoint current() {
            return current;
        }

        public void consume() {
            current = null;
        }

        String getName() {
            return name;
        }
    }

    public static void main(String args[]) {
        Map<String, List<DataPoint>> metriDps = new HashMap<>();
        String[] metricNames = new String[] {
                "m1", "m2", "m3"
        };
        List<DataPoint> dataPoints1 = new ArrayList<DataPoint>();
        dataPoints1.add(new DataPoint(0, 1));
        dataPoints1.add(new DataPoint(2, 1));
        dataPoints1.add(new DataPoint(3, 5));
        metriDps.put("m1", dataPoints1);

        List<DataPoint> dataPoints2 = new ArrayList<DataPoint>();
        dataPoints2.add(new DataPoint(1, 2));
        dataPoints2.add(new DataPoint(2, 3));
        dataPoints2.add(new DataPoint(3, 5));
        metriDps.put("m2", dataPoints2);

        List<DataPoint> dataPoints3 = new ArrayList<DataPoint>();
        dataPoints3.add(new DataPoint(0, 2));
        dataPoints3.add(new DataPoint(2, 6));
        dataPoints3.add(new DataPoint(4, 5));
        metriDps.put("m3", dataPoints3);

        StringWriter writer = new StringWriter();
        // header
        writer.append("timestamp,");
        writer.append(Stream.of(metricNames).collect(Collectors.joining(",")));
        writer.append('\n');

        List<NamedKeeparator> iterators = metriDps.entrySet().stream()
                .map(entry -> new NamedKeeparator(entry.getKey(), entry.getValue().iterator()))
                .collect(Collectors.toList());

        List<NamedKeeparator> leastIterators = new ArrayList<>();
        for (;;) {
            leastIterators.clear();
            long leastValue = Long.MAX_VALUE;
            for (NamedKeeparator iterator : iterators) {
                // advance until there is some value
                while (iterator.current() == null && iterator.hasNext()) {
                    iterator.next();
                }
                // build set of iterators pointing to least value
                if (iterator.current() != null
                        && iterator.current().getTimestamp() <= leastValue) {
                    if (iterator.current().getTimestamp() < leastValue) {
                        leastValue = iterator.current().getTimestamp();
                        leastIterators.clear();
                    }
                    leastIterators.add(iterator);
                }
            }
            // nothing -> all iterators done
            if (leastIterators.isEmpty())
                break;

            // least contains now iterators for the same timestamp

            // get time from the first
            long time = leastIterators.get(0).current().getTimestamp();
            writer.append(String.valueOf(time)).append(',');

            // format points
            String points = Stream.of(metricNames)
                    .map(metric -> leastIterators.stream()
                            .filter(it -> it.getName().equals(metric)).findAny()
                            .map(it -> it.current()).orElse(null))
                    .map(point -> point != null ? String.valueOf(point.getValue()) : "")
                    .collect(Collectors.joining(","));

            writer.append(points).append('\n');

            leastIterators.forEach(it -> {
                it.consume();
            });
        }

        System.out.println(writer);
    }

}
