/*
 * LensKit, an open source recommender systems toolkit.
 * Copyright 2010-2014 LensKit Contributors.  See CONTRIBUTORS.md.
 * Work on LensKit has been funded by the National Science Foundation under
 * grants IIS 05-34939, 08-08692, 08-12148, and 10-17697.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.lenskit.eval.metrics;

import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;

import javax.annotation.Nullable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * Utility class for converting typed results into row data for table writers.
 *
 * @author <a href="http://www.grouplens.org">GroupLens Research</a>
 * @since 2.1
 */
public class ResultConverter<T> {
    private final Class<T> resultType;
    private final List<Column> columns;
    private final List<String> columnLabels;

    ResultConverter(Class<T> type, List<Column> cols) {
        resultType = type;
        columns = ImmutableList.copyOf(cols);
        columnLabels = Lists.transform(columns, new Function<Column, String>() {
            @Nullable
            @Override
            public String apply(@Nullable Column input) {
                assert input != null;
                return input.getName();
            }
        });
    }

    public Class<T> getResultType() {
        return resultType;
    }

    /**
     * Get the labels of the columns produced by this converter.
     * @return The column labels.
     */
    public List<String> getColumnLabels() {
        return columnLabels;
    }

    /**
     * Get the column values for a result instance.
     * @param result The result instance.
     * @return The list of column values.
     */
    public List<Object> getColumns(T result) {
        List<Object> row = Lists.newArrayList();
        for (Column col: columns) {
            if (result != null) {
                row.add(col.getValue(result));
            } else {
                row.add(null);
            }
        }
        return row;
    }

    public static <T> ResultConverter<T> create(Class<T> type) {
        List<Column> columns = Lists.newArrayList();
        for (Method m: type.getMethods()) {
            ResultColumn info = m.getAnnotation(ResultColumn.class);
            if (info != null) {
                columns.add(new MethodColumn(m));
            }
        }
        for (Field f: type.getFields()) {
            ResultColumn info = f.getAnnotation(ResultColumn.class);
            if (info != null) {
                columns.add(new FieldColumn(f));
            }
        }
        Collections.sort(columns);

        return new ResultConverter<T>(type, columns);
    }

    private abstract static class Column implements Comparable<Column> {
        private final ResultColumn info;

        Column(AnnotatedElement elem) {
            info = elem.getAnnotation(ResultColumn.class);
        }

        public String getName() {
            return info.value();
        }

        @Override
        public int compareTo(Column o) {
            int col1 = info.order();
            if (col1 < 0) {
                col1 = Integer.MAX_VALUE;
            }
            int col2 = o.info.order();
            if (col2 < 0) {
                col2 = Integer.MAX_VALUE;
            }
            return Ints.compare(col1, col2);
        }

        public abstract Object getValue(Object obj);
    }

    private static class MethodColumn extends Column {
        private final Method method;

        MethodColumn(Method m) {
            super(m);
            method = m;
        }

        @Override
        public Object getValue(Object obj) {
            try {
                return method.invoke(obj);
            } catch (IllegalAccessException e) {
                throw Throwables.propagate(e);
            } catch (InvocationTargetException e) {
                throw Throwables.propagate(e.getCause());
            }
        }
    }

    private static class FieldColumn extends Column {
        private final Field field;

        FieldColumn(Field f) {
            super(f);
            field = f;
        }

        @Override
        public Object getValue(Object obj) {
            try {
                return field.get(obj);
            } catch (IllegalAccessException e) {
                throw Throwables.propagate(e);
            }
        }
    }
}

@@ -1230,23 +1230,24 @@
 mon.
-primitives.Ints
+collect.Ordering
 ;%0A%0Ai
@@ -1443,38 +1443,8 @@
 od;%0A
-import java.util.Collections;%0A
 impo
@@ -3506,24 +3506,25 @@
       %7D%0A
+%0A
         
 Collecti
@@ -3519,24 +3519,617 @@
     
-Collections.sort
+ImmutableList%3CColumn%3E sorted = %0A                Ordering.natural()%0A                        .onResultOf(new Function%3CColumn, Integer%3E() %7B%0A                            @Override%0A                            public Integer apply(@Nullable Column input) %7B%0A                                assert input != null;%0A                                int c = input.getInfo().order();%0A                                // negative values should sort last%0A                                return c %3E= 0 ? c : Integer.MAX_VALUE;%0A                            %7D%0A                        %7D)%0A                        .immutableSortedCopy
 (col
@@ -4135,17 +4135,16 @@
 lumns);%0A
-%0A
         
@@ -4174,17 +4174,16 @@
 ter%3C
-T
 %3E(type, 
 colu
@@ -4178,23 +4178,22 @@
 %3E(type, 
-columns
+sorted
 );%0A    %7D
@@ -4238,38 +4238,8 @@
 lumn
- implements Comparable%3CColumn%3E
  %7B%0A 
@@ -4479,346 +4479,63 @@
     
-@Override%0A        public int compareTo(Column o) %7B%0A            int col1 = info.order();%0A            if (col1 %3C 0) %7B%0A                col1 = Integer.MAX_VALUE;%0A            %7D%0A            int col2 = o.info.order();%0A            if (col2 %3C 0) %7B%0A                col2 = Integer.MAX_VALUE;%0A            %7D%0A            return Ints.compare(col1, col2)
+public ResultColumn getInfo() %7B%0A            return info
 ;%0A  

Clean up ResultConverter