/*
 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * 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.
 *
 * Contributors:
 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
 *
 * $Id$
 */

package org.nuxeo.ecm.core.api;

import java.text.Collator;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * DocumentModel comparator. Uses ordering independent of case or accent. If two values are integers/longs, numbering
 * comparison is used.
 *
 * @author Florent Guillaume
 * @author Anahide Tchertchian
 */
public class DocumentModelComparator implements Sorter {

    private static final long serialVersionUID = 1L;

    public static final String ORDER_ASC = "asc";

    static final Collator collator = Collator.getInstance();

    static {
        collator.setStrength(Collator.PRIMARY); // case+accent independent
    }

    final String schemaName;

    final Map<String, String> orderBy;

    /**
     * Constructor using a schema and a map of field names to compare on.
     *
     * @param schemaName the schema name
     * @param orderBy map using property names as keys, and "asc" or "desc" as values. Should be a {@link LinkedHashMap}
     *            if order of criteria matters.
     */
    public DocumentModelComparator(String schemaName, Map<String, String> orderBy) {
        this.schemaName = schemaName;
        this.orderBy = orderBy;
    }

    /**
     * Constructor using a map of property names to compare on.
     *
     * @param orderBy map using property names as keys, and "asc" or "desc" as values. Should be a {@link LinkedHashMap}
     *            if order of criteria matters.
     */
    public DocumentModelComparator(Map<String, String> orderBy) {
        this(null, orderBy);
    }

    protected int compare(Object v1, Object v2, boolean asc) {
        if (v1 == null && v2 == null) {
            return 0;
        } else if (v1 == null) {
            return asc ? -1 : 1;
        } else if (v2 == null) {
            return asc ? 1 : -1;
        }
        final int cmp;
        if (v1 instanceof Long && v2 instanceof Long) {
            cmp = ((Long) v1).compareTo((Long) v2);
        } else if (v1 instanceof Integer && v2 instanceof Integer) {
            cmp = ((Integer) v1).compareTo((Integer) v2);
        } else if (v1 instanceof Calendar && v2 instanceof Calendar) {
            cmp = ((Calendar) v1).compareTo((Calendar) v2);
        } else {
            cmp = collator.compare(v1.toString(), v2.toString());
        }
        return asc ? cmp : -cmp;
    }

    @Override
    public int compare(DocumentModel doc1, DocumentModel doc2) {
        if (doc1 == null && doc2 == null) {
            return 0;
        } else if (doc1 == null) {
            return -1;
        } else if (doc2 == null) {
            return 1;
        }

        int cmp = 0;
        if (schemaName != null) {
            DataModel d1 = doc1.getDataModel(schemaName);
            DataModel d2 = doc2.getDataModel(schemaName);
            for (Entry<String, String> e : orderBy.entrySet()) {
                final String fieldName = e.getKey();
                final boolean asc = ORDER_ASC.equals(e.getValue());
                Object v1 = d1.getData(fieldName);
                Object v2 = d2.getData(fieldName);
                cmp = compare(v1, v2, asc);
                if (cmp != 0) {
                    break;
                }
            }
        } else {
            for (Entry<String, String> e : orderBy.entrySet()) {
                final String propertyName = e.getKey();
                final boolean asc = ORDER_ASC.equals(e.getValue());
                Object v1 = null;
                try {
                    v1 = doc1.getPropertyValue(propertyName);
                } catch (PropertyException pe) {
                    v1 = null;
                }
                Object v2 = null;
                try {
                    v2 = doc2.getPropertyValue(propertyName);
                } catch (PropertyException pe) {
                    v2 = null;
                }
                cmp = compare(v1, v2, asc);
                if (cmp != 0) {
                    break;
                }
            }
        }
        if (cmp == 0) {
            // everything being equal, provide consistent ordering
            if (doc1.hashCode() == doc2.hashCode()) {
                cmp = 0;
            } else if (doc1.hashCode() < doc2.hashCode()) {
                cmp = -1;
            } else {
                cmp = 1;
            }
        }
        return cmp;
    }

}
