package org.sep4j.showcase;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.junit.Test;
import org.sep4j.CellError;
import org.sep4j.DatumError;
import org.sep4j.InvalidHeaderRowException;
import org.sep4j.Ssio;

import com.google.common.collect.ImmutableMap;

/**
 * for documentation only
 * 
 * @author chenjianjx
 */
public class SepShowcases {

	@Test
	public void save() throws IOException {
		doSave();
	}

	@Test
	public void saveWithGuava() throws IOException {

		Collection<User> userList = buildTestUsers();
		ByteArrayOutputStream spreadsheetOutputStream = new ByteArrayOutputStream();

		Ssio.save(ImmutableMap.of("userId", "User Id", "firstName",
				"First Name", "lastName", "Last Name"), userList, spreadsheetOutputStream);

		byte[] spreadsheet = spreadsheetOutputStream.toByteArray();
		File theFile = createFile("saveWithGuava");
		FileUtils.writeByteArrayToFile(theFile, spreadsheet);
		System.out.println("File saved as " + theFile.getAbsolutePath());

	}

	@Test
	public void saveAndAppend() throws IOException {

		Collection<User> userList = buildTestUsers();

		File theFile = createFile("saveAndAppend");
		ImmutableMap<String, String> headerMap = ImmutableMap.of("userId", "User Id", "firstName",
				"First Name", "lastName", "Last Name");

		Ssio.save(headerMap, userList, theFile);
		System.out.println("File saved as " + theFile.getAbsolutePath());

		Collection<User> newListToAppend = buildTestUsers();
		Ssio.appendTo(headerMap, newListToAppend, theFile);
		System.out.println("Records appended to " + theFile.getAbsolutePath());
	}



	@Test
	public void saveMaps() throws IOException {
		ByteArrayOutputStream spreadsheetOutputStream = new ByteArrayOutputStream();

		ImmutableMap<String, Object> record1 = ImmutableMap.of("firstName", "Jim", "lastName", "Green");
		ImmutableMap<String, Object> record2 = ImmutableMap.of("firstName", "Li", "lastName", "Lei");
		List<Map<String,Object>> records = Arrays.asList(record1, record2);
		ImmutableMap<String, String> headerMap = ImmutableMap.of("firstName", "First Name", "lastName", "Last Name");
		Ssio.saveMaps(headerMap, records, spreadsheetOutputStream);

		//You can also let sep4j generate a header map for you, by just providing the map's keys
		Ssio.saveMaps(Arrays.asList("firstName", "lastName"), records, spreadsheetOutputStream);

		byte[] spreadsheet = spreadsheetOutputStream.toByteArray();
		File theFile = createFile("saveMaps");
		FileUtils.writeByteArrayToFile(theFile, spreadsheet);
		System.out.println("File saved as " + theFile.getAbsolutePath());
	}


	@Test
	public void saveWithGeneratedHeaderMap() throws IOException {

		Collection<User> userList = buildTestUsers();
		ByteArrayOutputStream spreadsheetOutputStream = new ByteArrayOutputStream();

		Ssio.save(User.class, userList, spreadsheetOutputStream);

		byte[] spreadsheet = spreadsheetOutputStream.toByteArray();
		File theFile = createFile("saveWithGeneratedHeaderMap");
		FileUtils.writeByteArrayToFile(theFile, spreadsheet);
		System.out.println("File saved as " + theFile.getAbsolutePath());
	}
	

	@Test
	public void parse() throws IOException {
		File file = doSave();
		InputStream spreadsheetInputStream = toByteArrayInputStreamAndClose(new FileInputStream(
				file));

		Map<String, String> reverseHeaderMap = new HashMap<String, String>();
		reverseHeaderMap.put("User Id", "userId"); // "User Id" is a column
													// header in the
													// spreadsheet."userId" is
													// the corresponding
													// property of User class.
		reverseHeaderMap.put("First Name", "firstName");
		reverseHeaderMap.put("Last Name", "lastName");

		List<CellError> cellErrors = new ArrayList<CellError>();
		try {
			@SuppressWarnings("unused")
			List<User> users = Ssio.parse(reverseHeaderMap, spreadsheetInputStream,
					cellErrors, User.class);
		} catch (InvalidFormatException e) {
			System.err.println("Not a valid spreadsheet file");
		} catch (InvalidHeaderRowException e) {
			System.err
					.println("The column headers of your spreadsheet file donot match what we need");
		}

		for (CellError ce : cellErrors) {
			System.err
					.println(MessageFormat
							.format("failed to parse a cell: rowIndexOneBased = {0}, columnIndexOneBased = {1}, propName = \"{2}\", headerText = \"{3}\", cause = {4} ",
									ce.getRowIndexOneBased(),
									ce.getColumnIndexOneBased(),
									ce.getPropName(), ce.getHeaderText(),
									ce.getCause()));
		}

		// List<User> users = Ssio.parseIgnoringErrors(reverseHeaderMap,
		// inputStream, User.class);

		// System.out.println(users);
	}

	@Test
	public void parseWithGuava() throws IOException {
		File file = doSave();
		InputStream spreadsheetInputStream = toByteArrayInputStreamAndClose(new FileInputStream(
				file));

		List<CellError> cellErrors = new ArrayList<CellError>();
		try {
			@SuppressWarnings("unused")
			List<User> users = Ssio.parse(ImmutableMap.of("User Id", "userId",
					"First Name", "firstName", "Last Name", "lastName"),
					spreadsheetInputStream, cellErrors, User.class);
		} catch (InvalidFormatException e) {
			System.err.println("Not a valid spreadsheet file");
		} catch (InvalidHeaderRowException e) {
			System.err
					.println("The column headers of your spreadsheet file donot match what we need");
		}

		for (CellError ce : cellErrors) {
			System.err
					.println(MessageFormat
							.format("failed to parse a cell: rowIndexOneBased = {0}, columnIndexOneBased = {1}, propName = \"{2}\", headerText = \"{3}\", cause = {4} ",
									ce.getRowIndexOneBased(),
									ce.getColumnIndexOneBased(),
									ce.getPropName(), ce.getHeaderText(),
									ce.getCause()));
		}

		// List<User> users = Ssio.parseIgnoringErrors(reverseHeaderMap,
		// inputStream, User.class);

		// System.out.println(users);
	}
	
	
	@Test
	public void parseWithGeneratedReverseHeaderMap() throws IOException {
		File file = doSave();
		InputStream inputStream = toByteArrayInputStreamAndClose(new FileInputStream(file));
		List<User> users = Ssio.parseIgnoringErrors(inputStream, User.class);
		System.out.println(users);	 
	}


	@Test
	public void parseMaps() throws IOException {
		File file = doSave();
		InputStream inputStream = toByteArrayInputStreamAndClose(new FileInputStream(file));
		Map<String, String> reverseHeaderMap = ImmutableMap.of("User Id", "userId",
				"First Name", "firstName", "Last Name", "lastName");
		List<Map<String, String>> users = Ssio.parseToMapsIgnoringErrors(reverseHeaderMap,  inputStream);
		System.out.println(users);
	}

	private File doSave() throws IOException {
		Collection<User> userList = buildTestUsers();

		ByteArrayOutputStream spreadsheetOutputStream = new ByteArrayOutputStream();
		Map<String, String> headerMap = new LinkedHashMap<String, String>();
		headerMap.put("userId", "User Id"); // "userId" is a property of User
											// class. "User Id" will be the
											// corresponding column header in
											// the spreadsheet.
		headerMap.put("firstName", "First Name");
		headerMap.put("lastName", "Last Name");
		headerMap.put("birthDay", "Birth Date");
		headerMap.put("birthDayString", "Birth Date");

		// Ssio.save(headerMap, users, outputStream);

		/*** show case error handling ***/

		// ////to collect the errors
		List<DatumError> datumErrors = new ArrayList<DatumError>();
		headerMap.put("fakeProperty", "Fake Property"); // try to write an
														// non-exsting property
		Ssio.save(headerMap, userList, spreadsheetOutputStream, "!!ERROR!!", datumErrors);
		for (DatumError de : datumErrors) {// here to handle the errors
			System.err
					.println(MessageFormat
							.format("Error: recordIndex = {0}, propName = \"{1}\", cause = {2}",
									de.getRecordIndex(), de.getPropName(),
									de.getCause()));
		}

		byte[] spreadsheet = spreadsheetOutputStream.toByteArray();
		File theFile = createFile("save");
		FileUtils.writeByteArrayToFile(theFile, spreadsheet);
		return theFile;
	}

	private Collection<User> buildTestUsers() {
		User user1 = new User();
		user1.setUserId(1);
		user1.setFirstName("Lei");
		user1.setLastName("Li");

		User user2 = new User();
		user2.setUserId(2);
		user2.setFirstName("Jim");
		user2.setLastName("Green");

		Collection<User> userList = Arrays.asList(user1, user2);
		return userList;
	}

	private File createFile(String prefix) {
		File dir = new File(System.getProperty("java.io.tmpdir"), "/sep4j/show-case");
		dir.mkdirs();
		String filename = prefix + System.currentTimeMillis() + ".xlsx";
		File file = new File(dir, filename);
		return file;
	}

	private ByteArrayInputStream toByteArrayInputStreamAndClose(InputStream in) {
		try {
			byte[] bytes = IOUtils.toByteArray(in);
			return new ByteArrayInputStream(bytes);
		} catch (IOException e) {
			throw new IllegalStateException(e);
		} finally {
			IOUtils.closeQuietly(in);
		}

	}

	@SuppressWarnings("unused")
	private static final class User {

		private long userId;
		private String firstName;
		private String lastName;
		private Date birthDay;

		private List<String> roles;

		public long getUserId() {
			return userId;
		}

		public void setUserId(long userId) {
			this.userId = userId;
		}

		public String getFirstName() {
			return firstName;
		}

		public void setFirstName(String firstName) {
			this.firstName = firstName;
		}

		public String getLastName() {
			return lastName;
		}

		public void setLastName(String lastName) {
			this.lastName = lastName;
		}

		public Date getBirthDay() {
			return birthDay;
		}

		public String getBirthDayString() {
			if (birthDay == null) {
				return null;
			}
			return DateFormatUtils.format(birthDay, "yyyy-MM-dd");
		}

		/**
		 * if the cell is of Date type
		 * 
		 * @param birthDay
		 */
		public void setBirthDay(Date birthDay) {
			this.birthDay = birthDay;
		}

		/**
		 * if the cell is of String type
		 * 
		 * @param birthDayString
		 * @throws ParseException
		 */
		public void setBirthDay(String birthDayString) throws ParseException {
			if (birthDayString == null) {
				return;
			}
			birthDay = DateUtils.parseDate(birthDayString,
					new String[] { "yyyy-MM-dd" });
		}

		public List<String> getRoles() {
			return roles;
		}

		public void setRoles(List<String> roles) {
			this.roles = roles;
		}

		public void setRoles(String rolesString) {
			String[] roleArray = StringUtils.split(rolesString, ",");
			this.setRoles(Arrays.asList(roleArray));
		}

		@Override
		public String toString() {
			return ToStringBuilder.reflectionToString(this,
					ToStringStyle.SHORT_PREFIX_STYLE);
		}
	}

}