/*
 * Copyright 2007 Kasper B. Graversen
 * 
 * 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 org.supercsv.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvConstraintViolationException;
import org.supercsv.exception.SuperCsvException;

/**
 * Useful utility methods.
 * 
 * @author Kasper B. Graversen
 * @author James Bassett
 */
public final class Util {
	
	// no instantiation
	private Util() {
	}
	
	/**
	 * Processes each element in the source List (using the corresponding processor chain in the processors array) and
	 * adds it to the destination List. A <tt>null</tt> CellProcessor in the array indicates that no processing is
	 * required and the element should be added as-is.
	 * 
	 * @param destination
	 *            the List to add the processed elements to (which is cleared before it's populated)
	 * @param source
	 *            the List of source elements to be processed
	 * @param processors
	 *            the array of CellProcessors used to process each element. The number of elements in this array must
	 *            match the size of the source List. A <tt>null</tt> CellProcessor in this array indicates that no
	 *            processing is required and the element should be added as-is.
	 * @param lineNo
	 *            the current line number
	 * @param rowNo
	 *            the current row number
	 * @throws NullPointerException
	 *             if destination, source or processors is null
	 * @throws SuperCsvConstraintViolationException
	 *             if a CellProcessor constraint failed
	 * @throws SuperCsvException
	 *             if source.size() != processors.length, or CellProcessor execution failed
	 */
	public static void executeCellProcessors(final List<Object> destination, final List<?> source,
		final CellProcessor[] processors, final int lineNo, final int rowNo) {
		
		if( destination == null ) {
			throw new NullPointerException("destination should not be null");
		} else if( source == null ) {
			throw new NullPointerException("source should not be null");
		} else if( processors == null ) {
			throw new NullPointerException("processors should not be null");
		}
		
		// the context used when cell processors report exceptions
		final CsvContext context = new CsvContext(lineNo, rowNo, 1);
		context.setRowSource(new ArrayList<Object>(source));
		
		if( source.size() != processors.length ) {
			throw new SuperCsvException(String.format(
				"The number of columns to be processed (%d) must match the number of CellProcessors (%d): check that the number"
					+ " of CellProcessors you have defined matches the expected number of columns being read/written",
				source.size(), processors.length), context);
		}
		
		destination.clear();
		
		for( int i = 0; i < source.size(); i++ ) {
			
			context.setColumnNumber(i + 1); // update context (columns start at 1)
			
			if( processors[i] == null ) {
				destination.add(source.get(i)); // no processing required
			} else {
				destination.add(processors[i].execute(source.get(i), context)); // execute the processor chain
			}
		}
	}
	
	/**
	 * Converts a List to a Map using the elements of the nameMapping array as the keys of the Map.
	 * 
	 * @param destinationMap
	 *            the destination Map (which is cleared before it's populated)
	 * @param nameMapping
	 *            the keys of the Map (corresponding with the elements in the sourceList). Cannot contain duplicates.
	 * @param sourceList
	 *            the List to convert
	 * @param <T>
	 *            the type of the values in the map
	 * @throws NullPointerException
	 *             if destinationMap, nameMapping or sourceList are null
	 * @throws SuperCsvException
	 *             if nameMapping and sourceList are not the same size
	 */
	public static <T> void filterListToMap(final Map<String, T> destinationMap, final String[] nameMapping,
		final List<? extends T> sourceList) {
		if( destinationMap == null ) {
			throw new NullPointerException("destinationMap should not be null");
		} else if( nameMapping == null ) {
			throw new NullPointerException("nameMapping should not be null");
		} else if( sourceList == null ) {
			throw new NullPointerException("sourceList should not be null");
		} else if( nameMapping.length != sourceList.size() ) {
			throw new SuperCsvException(
				String
					.format(
						"the nameMapping array and the sourceList should be the same size (nameMapping length = %d, sourceList size = %d)",
						nameMapping.length, sourceList.size()));
		}
		
		destinationMap.clear();
		
		for( int i = 0; i < nameMapping.length; i++ ) {
			final String key = nameMapping[i];
			
			if( key == null ) {
				continue; // null's in the name mapping means skip column
			}
			
			// no duplicates allowed
			if( destinationMap.containsKey(key) ) {
				throw new SuperCsvException(String.format("duplicate nameMapping '%s' at index %d", key, i));
			}
			
			destinationMap.put(key, sourceList.get(i));
		}
	}
	
	/**
	 * Returns a List of all of the values in the Map whose key matches an entry in the nameMapping array.
	 * 
	 * @param map
	 *            the map
	 * @param nameMapping
	 *            the keys of the Map values to add to the List
	 * @return a List of all of the values in the Map whose key matches an entry in the nameMapping array
	 * @throws NullPointerException
	 *             if map or nameMapping is null
	 */
	public static List<Object> filterMapToList(final Map<String, ?> map, final String[] nameMapping) {
		if( map == null ) {
			throw new NullPointerException("map should not be null");
		} else if( nameMapping == null ) {
			throw new NullPointerException("nameMapping should not be null");
		}
		
		final List<Object> result = new ArrayList<Object>(nameMapping.length);
		for( final String key : nameMapping ) {
			result.add(map.get(key));
		}
		return result;
	}
	
	/**
	 * Converts a Map to an array of objects, adding only those entries whose key is in the nameMapping array.
	 * 
	 * @param values
	 *            the Map of values to convert
	 * @param nameMapping
	 *            the keys to extract from the Map (elements in the target array will be added in this order)
	 * @return the array of Objects
	 * @throws NullPointerException
	 *             if values or nameMapping is null
	 */
	public static Object[] filterMapToObjectArray(final Map<String, ?> values, final String[] nameMapping) {
		
		if( values == null ) {
			throw new NullPointerException("values should not be null");
		} else if( nameMapping == null ) {
			throw new NullPointerException("nameMapping should not be null");
		}
		
		final Object[] targetArray = new Object[nameMapping.length];
		int i = 0;
		for( final String name : nameMapping ) {
			targetArray[i++] = values.get(name);
		}
		return targetArray;
	}
	
	/**
	 * Converts an Object array to a String array (null-safe), by calling toString() on each element.
	 * 
	 * @param objectArray
	 *            the Object array
	 * @return the String array, or null if objectArray is null
	 */
	public static String[] objectArrayToStringArray(final Object[] objectArray) {
		if( objectArray == null ) {
			return null;
		}
		
		final String[] stringArray = new String[objectArray.length];
		for( int i = 0; i < objectArray.length; i++ ) {
			stringArray[i] = objectArray[i] != null ? objectArray[i].toString() : null;
		}
		
		return stringArray;
	}
	
	/**
	 * Converts an {@code List<Object>} to a String array (null-safe), by calling {@code toString()} on each element.
	 * 
	 * @param objectList
	 *            the List
	 * @return the String array, or null if objectList is null
	 */
	public static String[] objectListToStringArray(final List<?> objectList) {
		if( objectList == null ) {
			return null;
		}
		
		final String[] stringArray = new String[objectList.size()];
		for( int i = 0; i < objectList.size(); i++ ) {
			stringArray[i] = objectList.get(i) != null ? objectList.get(i).toString() : null;
		}
		
		return stringArray;
	}
	
}
