/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.app.bulkedit;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;

import org.dspace.authority.AuthorityValue;
import org.dspace.authority.factory.AuthorityServiceFactory;
import org.dspace.authority.service.AuthorityValueService;

/**
 * Utility class to store a line from a CSV file
 *
 * @author Stuart Lewis
 */
public class DSpaceCSVLine implements Serializable {
    /**
     * The item id of the item represented by this line. -1 is for a new item
     */
    private final UUID id;

    /**
     * The elements in this line in a hashtable, keyed by the metadata type
     */
    private final Map<String, ArrayList> items;

    protected transient final AuthorityValueService authorityValueService
        = AuthorityServiceFactory.getInstance().getAuthorityValueService();

    /**
     * ensuring that the order-sensible columns of the csv are processed in the correct order
     */
    private transient final Comparator<? super String> headerComparator = new Comparator<String>() {
        @Override
        public int compare(String md1, String md2) {
            // The metadata coming from an external source should be processed after the others
            AuthorityValue source1 = authorityValueService.getAuthorityValueType(md1);
            AuthorityValue source2 = authorityValueService.getAuthorityValueType(md2);

            int compare;
            if (source1 == null && source2 != null) {
                compare = -1;
            } else if (source1 != null && source2 == null) {
                compare = 1;
            } else {
                // the order of the rest does not matter
                compare = md1.compareTo(md2);
            }
            return compare;
        }
    };

    /**
     * Create a new CSV line
     *
     * @param itemId The item ID of the line
     */
    public DSpaceCSVLine(UUID itemId) {
        // Store the ID + separator, and initialise the hashtable
        this.id = itemId;
        items = new TreeMap<>(headerComparator);
//        this.items = new HashMap<String, ArrayList>();
    }

    /**
     * Create a new CSV line for a new item
     */
    public DSpaceCSVLine() {
        // Set the ID to be null, and initialise the hashtable
        this.id = null;
        this.items = new TreeMap<>(headerComparator);
    }

    /**
     * Get the item ID that this line represents
     *
     * @return The item ID
     */
    public UUID getID() {
        // Return the ID
        return id;
    }

    /**
     * Add a new metadata value to this line
     *
     * @param key   The metadata key (e.g. dc.contributor.author)
     * @param value The metadata value
     */
    public void add(String key, String value) {
        // Create the array list if we need to
        if (items.get(key) == null) {
            items.put(key, new ArrayList<String>());
        }

        // Store the item if it is not null
        if (value != null) {
            items.get(key).add(value);
        }
    }

    /**
     * Get all the values that match the given metadata key. Will be null if none exist.
     *
     * @param key The metadata key
     * @return All the elements that match
     */
    public List<String> get(String key) {
        // Return any relevant values
        return items.get(key);
    }

    /**
     * Get any action associated with this line
     *
     * @return The action (may be blank, 'withdraw', 'reinstate' or 'delete')
     */
    public String getAction() {
        if (items.containsKey("action")) {
            ArrayList actions = items.get("action");
            if (actions.size() > 0) {
                return ((String) actions.get(0)).trim();
            }
        }
        return "";
    }

    /**
     * Get all the metadata keys that are represented in this line
     *
     * @return An enumeration of all the keys
     */
    public Set<String> keys() {
        // Return the keys
        return items.keySet();
    }

    /**
     * Write this line out as a CSV formatted string, in the order given by the headings provided
     *
     * @param headings       The headings which define the order the elements must be presented in
     * @param fieldSeparator separator between metadata fields
     * @param valueSeparator separator between metadata values (within a field)
     * @return The CSV formatted String
     */
    protected String toCSV(List<String> headings, String fieldSeparator, String valueSeparator) {
        StringBuilder bits = new StringBuilder();

        // Add the id
        bits.append("\"").append(id).append("\"").append(fieldSeparator);
        bits.append(valueToCSV(items.get("collection"), valueSeparator));

        // Add the rest of the elements
        for (String heading : headings) {
            bits.append(fieldSeparator);
            List<String> values = items.get(heading);
            if (values != null && !"collection".equals(heading)) {
                bits.append(valueToCSV(values, valueSeparator));
            }
        }

        return bits.toString();
    }

    /**
     * Internal method to create a CSV formatted String joining a given set of elements
     *
     * @param values         The values to create the string from
     * @param valueSeparator value separator
     * @return The line as a CSV formatted String
     */
    protected String valueToCSV(List<String> values, String valueSeparator) {
        // Check there is some content
        if (values == null) {
            return "";
        }

        // Get on with the work
        String s;
        if (values.size() == 1) {
            s = values.get(0);
        } else {
            // Concatenate any fields together
            StringBuilder str = new StringBuilder();

            for (String value : values) {
                if (str.length() > 0) {
                    str.append(valueSeparator);
                }

                str.append(value);
            }

            s = str.toString();
        }

        // Replace internal quotes with two sets of quotes
        return "\"" + s.replaceAll("\"", "\"\"") + "\"";
    }
}

