/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.addthis.hydra.data.query.op;

import java.util.Comparator;
import java.util.StringTokenizer;

import com.addthis.basis.util.Strings;

import com.addthis.bundle.core.Bundle;
import com.addthis.bundle.core.BundleField;
import com.addthis.bundle.table.DataTable;
import com.addthis.bundle.table.DataTableFactory;
import com.addthis.bundle.util.BundleColumnBinder;
import com.addthis.bundle.util.ValueUtil;
import com.addthis.bundle.value.ValueObject;
import com.addthis.hydra.data.query.AbstractTableOp;


/**
 * <p>This query operation <span class="hydra-summary">performs an in-memory sort that uses disk
 * when necessary</span>.
 * <p/>
 * <p>The syntax is sort=[cols]:[type]:[direction]. [cols] is one or more columns
 * separated by commas. Type is a sequence of either "n" for numeric or "s" for string.
 * Direction is a sequence of either "a" for ascending or "d" for descending. The lengths
 * of [type] and [direction] must be equal to the number of column specified.
 * <p/>
 * <p>Example:</p>
 * <pre>
 * 0 A 3
 * 1 A 1
 * 1 B 2
 * 0 A 5
 *
 * sort=0,1,2:nsn:add
 *
 * 0 A 5
 * 0 A 3
 * 1 B 2
 * 1 A 1
 * </pre>
 *
 * @user-reference
 * @hydra-name sort
 */
public class OpSort extends AbstractTableOp {

    private final String[] cols;
    private final char[] type;
    private final char[] dir;

    public OpSort(DataTableFactory factory, String args) {
        super(factory);

        StringTokenizer st = new StringTokenizer(args, ":");
        cols = Strings.splitArray(st.hasMoreElements() ? st.nextToken() : "0", ",");

        String ts = st.hasMoreElements() ? st.nextToken() : "s";
        while (ts.length() < cols.length) {
            ts = ts.concat(ts.substring(0, 1));
        }
        type = ts.toCharArray();

        String ds = st.hasMoreElements() ? st.nextToken() : "a";
        while (ds.length() < cols.length) {
            ds = ds.concat(ds.substring(0, 1));
        }
        dir = ds.toCharArray();
    }

    @Override
    public DataTable tableOp(final DataTable result) {
        result.sort(new Comparator<Bundle>() {
            // TODO temp hack b/c table appends are BROKEN ATM WRT table.getFormat()
//          private final BundleField columns[] = new BundleColumnBinder(result, cols).getFields();
            private BundleField columns[];

            public int compare(Bundle o1, Bundle o2) {
                if (columns == null) {
                    columns = new BundleColumnBinder(o1, cols).getFields();
                }
                int delta = 0;
                for (int i = 0; i < columns.length; i++) {
                    BundleField col = columns[i];
                    if (delta == 0) {
                        switch (type[i]) {
                            case 'i': // int
                            case 'l': // long
                            case 'n': // legacy "number"
                                delta = longCompare(o1.getValue(col), o2.getValue(col));
                                break;
                            case 'd': // double
                            case 'f': // float
                                delta = doubleCompare(o1.getValue(col), o2.getValue(col));
                                break;
                            case 's': // string
                            default:
                                delta = stringCompare(o1.getValue(col), o2.getValue(col));
                                break;
                        }

                        if (dir[i] == 'd') {
                            delta = -delta;
                        }
                    } else {
                        break;
                    }
                }
                return delta;
            }
        });
        return result;
    }

    /**
     * @param s1
     * @param s2
     * @return
     */
    private int longCompare(ValueObject s1, ValueObject s2) {
        if (s1 == s2) {
            return 0;
        }
        if (s1 == null) {
            return 1;
        }
        if (s2 == null) {
            return -1;
        }
        return ValueUtil.asNumberOrParseLong(s1, 10).asLong().getLong().compareTo(ValueUtil.asNumberOrParseLong(s2, 10).asLong().getLong());
    }

    /**
     * @param s1
     * @param s2
     * @return
     */
    private int doubleCompare(ValueObject s1, ValueObject s2) {
        if (s1 == s2) {
            return 0;
        }
        if (s1 == null) {
            return 1;
        }
        if (s2 == null) {
            return -1;
        }
        return ValueUtil.asNumberOrParseDouble(s1).asDouble().getDouble().compareTo(ValueUtil.asNumberOrParseDouble(s2).asDouble().getDouble());
    }

    /**
     * @param s1
     * @param s2
     * @return
     */
    private int stringCompare(ValueObject s1, ValueObject s2) {
        if (s1 == OpPivot.MIN || s2 == OpPivot.MAX) {
            return -1;
        }
        if (s1 == OpPivot.MAX || s2 == OpPivot.MIN) {
            return 1;
        }
        if (s1 == s2) {
            return 0;
        }
        if (s1 == null) {
            return 1;
        }
        if (s2 == null) {
            return -1;
        }
        return s1.toString().compareTo(s2.toString());
    }
}

@@ -1448,16 +1448,106 @@
 cending.
+%0A * The columns are delimited by commas and the types and directions have%0A * no delimiter.
  The len
@@ -1617,16 +1617,17 @@
 f column
+s
  specifi

Clean up of Javadoc description of "sort" query operation.