public class Lang {

	public static final String ERROR_MESSAGE_NO_DIRECTOR = "There is no director registered. You cannot register for an account yet.";
	public static final String ERROR_INVALID_PRIMARY_REGISTER_EMAIL=  "Not a valid email, try logging in with your student account";
	public static final String ERROR_ABSENCE_FOR_NULL_USER = "Tried to create absence for null user";
} public class DataTrain {

	private static class CacheKey implements Serializable {

		/**
		 * 
		 */
		private static final long serialVersionUID = -8879640511900898415L;

		@SuppressWarnings("unused")
		public String clazz;

		@SuppressWarnings("unused")
		public int id;

		public CacheKey(Class<?> clazz, int id) {
			this.clazz = clazz.getName();
			this.id = id;
		}
	}

	/**
	 * Transaction wrapper to keep in the train theme
	 * 
	 * For internal use in controllers when the outside code has already started
	 * a transaction when the controller tries to start one
	 * 
	 * Thus to deal with this nested transaction problem, we just linearize
	 * everything and let the outside code call the actual commit to commit
	 * everything.
	 * 
	 */
	private class InternalTrack extends Track {

		protected InternalTrack(Track t) {
			super(t.getTransaction());
		}

		/**
		 * Does nothing, this is a nested transaction
		 */
		@Override
		public void bendIronBack() {
			// Do nothing, this is a nested transaction
		}

		/**
		 * Does nothing, this is a nested transaction
		 */
		@Override
		public Future<Void> bendIronBackAsync() {
			// Do nothing, this is a nested transaction
			return null;
		}

	}

	/**
	 * Transaction wrapper to keep in the train theme
	 * 
	 */
	public class Track {

		private Transaction transaction;

		protected Track(Transaction t) {
			this.transaction = t;
		}

		/**
		 * Commits this transaction to the datastore
		 */
		public void bendIronBack() {
			this.transaction.commit();
		}

		/**
		 * Commits this transaction to the datastore asynchronously
		 */
		public Future<Void> bendIronBackAsync() {
			return this.transaction.commitAsync();
		}

		/**
		 * End this transaction without committing any of the changes made
		 */
		public void derail() {
			this.transaction.rollback();
		}

		/**
		 * End this transaction asynchronously without committing any of the
		 * changes made
		 */
		public Future<Void> derailAsync() {
			return this.transaction.rollbackAsync();
		}

		/**
		 * 
		 * @return The application id for the Transaction.
		 */
		public String getApp() {
			return this.transaction.getApp();
		}

		/**
		 * 
		 * @return The globally unique identifier for the Transaction.
		 */
		public String getId() {
			return this.transaction.getId();
		}

		protected Transaction getTransaction() {
			return this.transaction;
		}

		public boolean isActive() {
			return this.transaction.isActive();
		}

	}

	public static DataTrain depart() {
		DataTrain train = new DataTrain();
		return train;
	}

	private StandardObjectDatastore datastore = null;

	private Cache cache = null;

	/**
	 * Current transaction
	 */
	private Track track = null;

	public DataTrain() {
		datastore = ModelFactory.newObjectDatastore();
	}

	<T> RootFindCommand<T> find(Class<T> type) {
		return getDataStore().find().type(type);
	}

	public AbsenceManager absences() {
		return new AbsenceManager(this);
	}

	public AppDataManager appData() {
		return new AppDataManager(this);
	}

	public AuthManager auth() {
		return new AuthManager(this);
	}

	public DataManager data() {
		return new DataManager(this);
	}

	StandardObjectDatastore getDataStore() {
		return this.datastore;
	}

	public EventManager events() {
		return new EventManager(this);
	}

	public FormManager forms() {
		return new FormManager(this);
	}

	Cache getMemCache() {
		if (this.cache == null) {
			try {
				CacheFactory cacheFactory = CacheManager.getInstance()
						.getCacheFactory();
				this.cache = cacheFactory.createCache(Collections.emptyMap());
			} catch (CacheException e) {
				return null;
			}
		}

		return this.cache;
	}

	public MobileDataManager mobileData() {
		return new MobileDataManager(this);
	}

	// For implementing transaction support
	// Object getAncestor() {
	// return getVersionController().getCurrent();
	// }

	Key getTie(Class<?> type, long id) {
		return new KeyFactory.Builder(this.datastore.getConfiguration()
				.typeToKind(type), id).getKey();
	}

	Key getTie(Class<?> type, String id) {
		return new KeyFactory.Builder(this.datastore.getConfiguration()
				.typeToKind(type), id).getKey();
	}

	public UserManager users() {
		return new UserManager(this);
	}

	@SuppressWarnings("unchecked")
	<CachedType> CachedType loadFromCache(Class<CachedType> clazz, int id) {
		Cache cache = getMemCache();

		if (cache != null)
			return (CachedType) cache.get(new CacheKey(clazz, id));
		else
			return null;
	}

	/**
	 * Starts a new data store transaction
	 * 
	 * Note that only one transaction can be active at a time
	 */
	public Track switchTracks() {

		this.track = new Track(getDataStore().beginTransaction());
		return this.track;
	}

	/**
	 * Non-recursive begin transaction
	 * 
	 * Starts a new data store transaction if none is running, or simply reuses
	 * the existing one in a wrapper if it is active.
	 * 
	 * This is because only one transaction can be running at a time
	 * 
	 * @return Internal transaction for use in a controller. Must either be
	 *         committed to or canceled within the controller and never be
	 *         passed out of the controller
	 */
	Track switchTracksInternal() {
		if (this.track != null && this.track.isActive())
			return new InternalTrack(this.track);
		else
			return switchTracks();
	}

	<CachedType> boolean updateCache(int id, CachedType object) {

		if (object == null) {
			return false;
		}

		Class<?> clazz = object.getClass();

		Cache cache = getMemCache();

		if (cache != null) {
			cache.put(new CacheKey(clazz, id), object);

			return true;
		}

		return false;
	}
} public class UserManager extends AbstractManager {

	DataTrain datatrain;
	private static final Logger LOG = Logger.getLogger(UserManager.class
			.getName());

	// a user may not be later than this without 'missing' the whole rehearsal
	private static final int MAXIMUM_LATENESS_MINUTES = 30;

	public UserManager(DataTrain dataTrain) {
		this.datatrain = dataTrain;
	}

	public User createDirector(String schoolEmail, String loginEmail,
			String firstName, String lastName) throws IllegalArgumentException {

		// ValiDateTime email
		Email primaryEmail = Util.makeEmail(schoolEmail);
		Email secondaryEmail = Util.makeEmail(loginEmail);

		if (!ValidationUtil.isValidPrimaryEmail(primaryEmail, this.datatrain)) {
			throw new IllegalArgumentException("Invalid primary email");
		}

		if (!ValidationUtil.isValidSecondaryEmail(secondaryEmail,
				this.datatrain)) {
			throw new IllegalArgumentException("Invalid second email");
		}

		if (!ValidationUtil.isUniqueSecondaryEmail(secondaryEmail,
				primaryEmail, this.datatrain)) {
			throw new IllegalArgumentException("Non-unique secondary email");
		}

		// Check no duplicate users exist
		if (get(primaryEmail) != null)
			throw new IllegalArgumentException(
					"User already exists in the system");

		User user = ModelFactory.newUser(User.Type.Director, primaryEmail,
				"000000000");
		user.setFirstName(firstName);
		user.setLastName(lastName);
		if (secondaryEmail != null && secondaryEmail.getEmail() != "") {
			user.setSecondaryEmail(secondaryEmail);
		}

		validateUser(user);

		this.datatrain.getDataStore().store(user);

		return user;
	}

	public User createFakeStudent(String[] parts) {
		// first name, last name, primary email, secondary email, type,
		// section, uid, year, major, rank, minutesavailable
		String firstName = parts[0];
		String lastName = parts[1];
		Email primaryEmail = Util.makeEmail(parts[2]);
		Email secondaryEmail = Util.makeEmail(parts[3]);
		// String type = parts[4];// just in case we want it
		User.Section section = User.Section.valueOf(parts[5]);
		String univID = parts[6];
		int year = Integer.parseInt(parts[7]);
		String major = parts[8];

		String rank = parts[9];// just in case
		int minutesAvailable = Integer.parseInt(parts[10]);// just in case

		User user = ModelFactory.newUser(User.Type.Student, primaryEmail,
				univID);
		user.setFirstName(firstName);
		user.setLastName(lastName);
		user.setSecondaryEmail(secondaryEmail);
		user.setSection(section);
		user.setYear(year);
		user.setMajor(major);
		user.setRank(rank);
		user.setMinutesAvailable(minutesAvailable);
		user.setGrade(User.Grade.A);

		validateUser(user);

		this.datatrain.getDataStore().store(user);
		return user;
	}

	public User createStudent(com.google.appengine.api.users.User google_user,
			String univID, String firstName, String lastName, int year,
			String major, User.Section section, Email secondaryEmail)
			throws IllegalArgumentException {

		// Check google user
		if (google_user == null)
			throw new IllegalArgumentException("Must give a google user");

		// Extract email
		Email email = Util.makeEmail(google_user.getEmail());

		return createStudent(email, univID, firstName, lastName, year, major,
				section, secondaryEmail);
	}

	public User createStudent(Email primaryEmail, String univID,
			String firstName, String lastName, int year, String major,
			User.Section section, Email secondaryEmail)
			throws IllegalArgumentException {

		// Check google user
		if (primaryEmail == null)
			throw new IllegalArgumentException("Must give a google user");

		// Check no duplicate users exist
		if (get(primaryEmail) != null)
			throw new IllegalArgumentException(
					"User already exists in the system");

		User user = ModelFactory.newUser(User.Type.Student, primaryEmail,
				univID);
		user.setFirstName(firstName);
		user.setLastName(lastName);
		user.setYear(year);
		user.setMajor(major);
		user.setSection(section);
		user.setSecondaryEmail(secondaryEmail);

		// Just assume we start with an A
		user.setGrade(User.Grade.A);

		validateUser(user);

		this.datatrain.getDataStore().store(user);
		return user;
	}

	/*
	 * Full cleanup of user happens without affecting other things in the
	 * datastore
	 * 
	 * This includes:
	 * 
	 * Absences, Forms, mobile data uploads
	 */
	public void delete(User user) {
		ObjectDatastore od = this.datatrain.getDataStore();

		// Absences
		this.datatrain.absences().delete(user);

		// Forms
		this.datatrain.forms().delete(user);

		// Remove any Mobile Data uploads
		this.datatrain.mobileData().scrubUploader(user);

		od.delete(user);
	}

	/**
	 * 
	 * @param email
	 *            Primary email of a user
	 * @return
	 */
	public User get(Email primaryEmail) {

		User u;

		if (primaryEmail == null)
			return null;

		if (primaryEmail.getEmail() == null)
			return null;

		Iterator<User> users = this.datatrain
				.find(User.class)
				.addFilter(User.FIELD_PRIMARY_EMAIL, FilterOperator.EQUAL,
						primaryEmail.getEmail()).fetchMaximum(2).now();

		if (users.hasNext())
			u = users.next();
		else
			return null;

		if (users.hasNext())
			throw new IllegalStateException(
					"Found more than one user corresponding to primary email "
							+ primaryEmail);

		return u;
	}

	/**
	 * 
	 * @param netid
	 * @return User object for the netid, or null if there is no such netid
	 */
	public User get(String netid) {

		return this.datatrain.getDataStore().load(
				this.datatrain.getTie(User.class, netid));
	}

	public List<User> get(User.Type... types) {

		RootFindCommand<User> find = this.datatrain.find(User.class);
		find.addFilter(User.FIELD_TYPE, FilterOperator.IN, Arrays.asList(types));

		return find.returnAll().now();
	}

	public List<User> getAll() {
		return this.datatrain.find(User.class).returnAll().now();
	}

	/**
	 * 
	 * @param email
	 *            Secondary email of a user
	 * @return
	 */
	public User getSecondary(Email secondaryEmail) {

		User u;

		if (secondaryEmail == null)
			return null;

		if (secondaryEmail.getEmail() == null)
			return null;

		Iterator<User> users = this.datatrain
				.find(User.class)
				.addFilter(User.FIELD_SECONDARY_EMAIL, FilterOperator.EQUAL,
						secondaryEmail.getEmail()).fetchMaximum(2).now();

		if (users.hasNext())
			u = users.next();
		else
			return null;

		if (users.hasNext())
			throw new IllegalStateException(
					"Found more than one user corresponding to secondary email "
							+ secondaryEmail);

		return u;
	}

	private User.Grade intToGrade(int count, User student) {
		// users may miss up to 160 minutes for free (two rehearsals)
		int autoexcused = 160;
		count -= autoexcused;

		// give credit for the number of minutes worked
		count -= student.getMinutesAvailable();

		if (count <= 0) {
			return User.Grade.A;
		} else if (count <= 15) {
			return User.Grade.B;
		} else if (count <= 30) {
			return User.Grade.C;
		} else if (count <= 45) {
			return User.Grade.D;
		} else {
			return User.Grade.F;
		}
	}

	public boolean isUniqueId(String id, Email primary) {
		RootFindCommand<User> find = this.datatrain.find(User.class);
		find.addFilter(User.FIELD_UNIVERSITY_ID, FilterOperator.EQUAL, id);
		List<User> found = find.returnAll().now();
		// When we are registering we need this result to be zero because that
		// means
		// the person registering is the one who has it. But when we are just
		// validating stuff
		// this number can be 1, meaning the current person
		return found.size() == 0
				|| (found.size() == 1 && found.get(0).getPrimaryEmail()
						.equals(primary));
	}

	public boolean isUniqueSecondaryEmail(Email secondaryEmail, Email primary) {
		if (secondaryEmail == null || null == secondaryEmail.getEmail()
				|| "".equals(secondaryEmail.getEmail()))
			return true;

		RootFindCommand<User> find = this.datatrain.find(User.class);
		find.addFilter(User.FIELD_SECONDARY_EMAIL, FilterOperator.EQUAL,
				secondaryEmail);
		find.addFilter(User.FIELD_PRIMARY_EMAIL, FilterOperator.NOT_EQUAL,
				primary);

		int found = find.returnCount().now();
		return found == 0;
	}

	public void update(User u) {
		validateUser(u);
		updateUserGrade(u);
		this.datatrain.getDataStore().update(u);
	}

	/**
	 * Always calculated based on the attendance stored in the database. There
	 * should never be a reason for a user to override a grade manually.
	 * 
	 * Note that this DOES NOT currently refresh the student in the database.
	 */
	private void updateUserGrade(User student) {
		AbsenceManager ac = this.datatrain.absences();
		int minutes = 0;
		List<Absence> absences = ac.get(student);

		Map<Event, List<Absence>> map = new HashMap<Event, List<Absence>>(
				absences.size());
		for (Absence a : absences) {
			Event e = a.getEvent();
			if (e == null) {
				// it's unlinked!
			} else {
				if (map.containsKey(e)) {
					map.get(e).add(a);
				} else {
					List<Absence> list = new ArrayList<Absence>();
					list.add(a);
					map.put(e, list);
				}
			}
		}

		for (Event e : map.keySet()) {
			List<Absence> l = map.get(e);
			if (l.size() < 1) {
				throw new IllegalStateException(
						"Somehow the event had no associated absences");
			}
			if (l.size() == 1) {
				minutes += simpleGrade(e, l, student);
			} else {
				DateTimeZone zone = datatrain.appData().get().getTimeZone();
				String types = "";
				for (Absence a : l) {
					types += a.getType() + " ";
				}
				LOG.info("There were " + l.size() + " absences for "
						+ student.getId() + " during event "
						+ e.getInterval(zone).getStart() + " to "
						+ e.getInterval(zone).getEnd());
				LOG.info("absence types: " + types);
				minutes += generalGrade(e, l);
			}
		}
		student.setMinutesMissed(minutes);
		student.setGrade(intToGrade(minutes, student));
	}

	private int generalGrade(Event e, List<Absence> l) {
		// TODO this method does not properly handle tardies/ecos that have been
		// approved

		class PresenceInterval {

			private boolean present;
			private Interval interval;

			public PresenceInterval(Interval interval, boolean present) {
				this.setInterval(interval);
				this.setPresent(present);
			}

			public boolean isPresent() {
				return present;
			}

			public void setPresent(boolean present) {
				this.present = present;
			}

			public Interval getInterval() {
				return interval;
			}

			public void setInterval(Interval interval) {
				this.interval = interval;
			}

			@Override
			public String toString() {
				return (present ? "present " : "absent ") + interval.getStart()
						+ interval.getEnd();
			}
		}

		DateTimeZone zone = datatrain.appData().get().getTimeZone();

		for (Absence a : l) {
			if (a.getType().isAbsence()) {
				if (l.size() > 2) {
					LOG.info("There were " + l.size()
							+ " absences for a user during event "
							+ e.getInterval(zone));
				}
				return (int) e.getInterval(zone).toDuration()
						.getStandardMinutes();
			}
		}

		List<PresenceInterval> ints = new ArrayList<PresenceInterval>();
		ints.add(new PresenceInterval(e.getInterval(zone), true));
		for (Absence a : l) {
			// if (a.getType() == Absence.Type.Absence) {
			// throw new IllegalArgumentException(
			// "there were multiple absences of type Absence for a single event!");
			// }
			if (a.getStatus() == Absence.Status.Approved) {
				// skip it
				continue;
			}
			DateTime d = a.getType() == Absence.Type.Tardy ? a.getCheckin(zone)
					: a.getCheckout(zone);

			for (int i = 0; i < ints.size(); i++) {
				PresenceInterval p = ints.get(i);
				if (p.getInterval().contains(d)) {
					// found a match
					ints.remove(p);
					Interval first = new Interval(p.getInterval().getStart(), d);
					Interval second = new Interval(d, p.getInterval().getEnd());
					if (a.getType() == Absence.Type.Tardy) {
						// they were present AFTER the checkin
						ints.add(new PresenceInterval(first, false));
						ints.add(new PresenceInterval(second, true));
					} else {
						// they were present BEFORE the checkout
						ints.add(new PresenceInterval(first, true));
						ints.add(new PresenceInterval(second, false));
					}
					break;
					// move on to the next absence
				}
			}
		}
		int minutes = 0;
		for (PresenceInterval p : ints) {
			if (!p.isPresent()) {
				minutes += p.getInterval().toDuration().getStandardMinutes();
			}
		}

		if (minutes > MAXIMUM_LATENESS_MINUTES) {
			// too late!
			return (int) e.getInterval(zone).toDuration().getStandardMinutes();
		} else {
			return minutes;
		}
	}

	private int simpleGrade(Event e, List<Absence> l, User student) {
		if (l.size() != 1) {
			throw new IllegalArgumentException(
					"list of Absences must be of size 1");
		}
		int minutes = 0;
		DateTimeZone zone = datatrain.appData().get().getTimeZone();
		Absence a = l.get(0);
		if (a.getStatus() == Absence.Status.Approved) {
			// nothing to do, it's approved!
		} else if (a.getStatus() == Absence.Status.Pending
				|| a.getStatus() == Absence.Status.Denied) {

			// this means that absences with unanchored
			// events will have no grade penalty

			if (a.getEvent() != null) {
//				if (a.getType() == Absence.Type.Absence) {
//					minutes += a.getEvent().getInterval(zone).toDuration()
//							.getStandardMinutes();
//
//				} else {
					Duration d;
					if (a.getType() == Absence.Type.Tardy) {
						d = new Duration(a.getEvent().getInterval(zone)
								.getStart(), a.getCheckin(zone));
					} else if (a.getType() == Absence.Type.EarlyCheckOut) {
						d = new Duration(a.getCheckout(zone), a.getEvent()
								.getInterval(zone).getEnd());
					} else if (a.getType() == Absence.Type.Absence) {
						d = new Duration(a.getInterval(zone));
					}
					else {
						d = null;
						throw new IllegalArgumentException(
								"invalid absence type");
					}

					int minutesForTardyOrEco = 0;
					// in case there's an approved form, and they showed up
					// before or after the actual time by a bit
					List<Form> forms = datatrain.forms().get(student);
					for (Form f : forms) {
						if (f.getStatus() == Form.Status.Approved
								&& f.getType() == Form.Type.ClassConflict) {
							// Interval ei = e.getInterval(zone);
							// Interval fi = f.getInterval(zone);
							// f.getStartTime();
							// f.getEndTime();
							//
							// f.getDayOfWeek();
							// Interval ai;
							// // get interval of time absent
							// if (a.getType() == Type.Absence) {
							// ai = a.getInterval(zone);
							// } else if (a.getType() == Type.Tardy) {
							// ai = new Interval(e.getInterval(zone)
							// .getStartMillis(), a.getCheckin(zone)
							// .getMillis(), zone);
							// } else {
							// ai = new Interval(a.getCheckout(zone)
							// .getMillis(), e.getInterval(zone)
							// .getEndMillis(), zone);
							// }
							//
							// Interval lap = fi.overlap(ai);
							// if (lap != null) {
							// Duration overlap = lap.toDuration();
							// minutesForTardyOrEco -= overlap
							// .getStandardMinutes();
							// }

							Interval effectiveInterval = DataTrain.depart()
									.absences()
									.getEffectiveFormInterval(a, f, zone);

							Interval absence = DataTrain.depart().absences()
									.getAbsenceInterval(a, e, zone);

							Interval olap = effectiveInterval.overlap(absence);
							if (null != olap) {
								// there is some overlap, but apparently not
								// enough to just approve the absence

								// subtract the number of minutes that the form
								// DOES intersect with the absence (which we
								// consider to be approved)
								minutesForTardyOrEco -= olap.toDuration()
										.getStandardMinutes();
							}

						}
					}
					minutesForTardyOrEco += d.getStandardMinutes();

					if (minutesForTardyOrEco > MAXIMUM_LATENESS_MINUTES) {
						// too late!
						minutes += a.getEvent().getInterval(zone).toDuration()
								.getStandardMinutes();
					} else {
						minutes += minutesForTardyOrEco;
					}
				}
//			}
		}
		return minutes;
	}

	private void validateUser(User user) throws IllegalArgumentException {

		if (user == null)
			throw new IllegalArgumentException("Invalid user");

		// Check primary email
		if (!ValidationUtil.isValidPrimaryEmail(user.getPrimaryEmail(),
				this.datatrain))
			throw new IllegalArgumentException("Invalid primary email");

		// Check name
		if (!ValidationUtil.isValidName(user.getFirstName()))
			throw new IllegalArgumentException("Invalid first name");
		if (!ValidationUtil.isValidName(user.getLastName()))
			throw new IllegalArgumentException("Invalid last name");

		// Check id
		String uId = user.getUniversityID();
		if (!ValidationUtil.isValidUniversityID(uId)) {
			throw new IllegalArgumentException("Invalid university id");
		}

		if (user.getType() != User.Type.Director
				&& !ValidationUtil.isUniqueId(uId, user.getPrimaryEmail())) {
			throw new IllegalArgumentException("University id was not unique");
		}

		// Check secondary email
		if (!ValidationUtil.isValidSecondaryEmail(user.getSecondaryEmail(),
				this.datatrain)) {
			throw new IllegalArgumentException("Invalid secondary email");
		}
		if (!ValidationUtil.isUniqueSecondaryEmail(user.getSecondaryEmail(),
				user.getPrimaryEmail(), datatrain)) {
			throw new IllegalArgumentException("Non-unique secondary email");
		}

		// Check student specific things
		if (user.getType() == User.Type.Student) {
			String major = user.getMajor();
			String rank = user.getRank();
			User.Section section = user.getSection();
			int year = user.getYear();

			if (!ValidationUtil.isValidMajor(major)) {
				throw new IllegalArgumentException("Invalid major");
			}
			if (!ValidationUtil.isValidRank(rank)) {
				throw new IllegalArgumentException("Invalid rank");
			}
			if (!ValidationUtil.isValidSection(section)) {
				throw new IllegalArgumentException("Invalid section");
			}
			if (!ValidationUtil.isValidYear(year)) {
				throw new IllegalArgumentException("Invalid year");
			}
		}
	}
} @Entity(kind = "Absence", allocateIdsBy = 0)
public class Absence {

	public static enum Status {
		Pending, Approved, Denied;

		private String mDisplayString;

		private Status() {
			mDisplayString = this.toString();
		}

		public String getDisplayName() {
			return mDisplayString;
		}

		public String getValue() {
			return name();
		}

		public boolean isApproved() {
			return this == Approved;
		}

		public boolean isDenied() {
			return this == Denied;
		}

		public boolean isPending() {
			return this == Pending;
		}
	}

	public enum Type {
		Absence, Tardy, EarlyCheckOut;

		private String mDisplayString;

		private Type() {
			mDisplayString = this.toString();
		}

		private Type(String display_string) {
			mDisplayString = display_string;
		}

		public String getDisplayName() {
			return mDisplayString;
		}

		public String getValue() {
			return name();
		}

		public boolean isAbsence() {
			return Absence.equals(this);
		}

		public boolean isEarlyCheckOut() {
			return EarlyCheckOut.equals(this);
		}

		public boolean isTardy() {
			return Tardy.equals(this);
		}
	};

	public static final String FIELD_EVENT = "event";
	public static final String FIELD_STUDENT = "student";
	public static final String FIELD_TYPE = "type";

	@Id
	private long id;

	private Type type;

	private Status status;

	@Index
	@Activate
	private Event event;

	@Index
	@Activate
	private User student;

	private Date start;

	private Date end;

	@Store(false)
	private DateTime cachedCheckout;

	@Store(false)
	private DateTime cachedCheckin;
	@Store(false)
	private Interval cachedInterval;

	/**
	 * Create absence through AbsenceController (DataModel.absence().create(...)
	 */
	Absence() {
	}

	public static boolean isContainedIn(Absence a, ReadableInterval i) {
		if (a.getType() == null || i == null)
			return false;

		Chronology chron = i.getChronology();

		switch (a.getType()) {
		case Absence:
			return i.contains(a.getInterval(chron));
		case EarlyCheckOut:
			DateTime checkout = a.getCheckout(chron);
			return i.contains(checkout);
		case Tardy:
			DateTime checkin = a.getCheckin(chron);
			return i.contains(checkin);
		default:
			throw new UnsupportedOperationException();
		}
	}

	public DateTime getCheckin(DateTimeZone zone) {
		if (getType().isTardy())
			return new DateTime(this.start, zone);
		else
			throw new IllegalStateException("Intervals only valid for absences");
	}

	public DateTime getCheckin(Chronology chron) {
		if (getType().isTardy())
			return new DateTime(this.start, chron);
		else
			throw new IllegalStateException("Intervals only valid for absences");
	}

	public DateTime getCheckout(DateTimeZone zone) {
		if (getType().isEarlyCheckOut())
			return new DateTime(this.start, zone);
		else
			throw new IllegalStateException(
					"Checkouts only valid for early checkouts");
	}

	public DateTime getCheckout(Chronology chron) {
		if (getType().isEarlyCheckOut())
			return new DateTime(this.start, chron);
		else
			throw new IllegalStateException("Intervals only valid for absences");
	}

	@Deprecated
	public Date getDatetime() {
		return start;
	}

	@Deprecated
	public Date getEnd() {
		return end;
	}

	public Event getEvent() {
		return event;
	}

	public long getId() {
		return id;
	}

	/**
	 * Time-span of an absence
	 */
	public Interval getInterval(DateTimeZone zone) {
		if (getType().isAbsence()) {
			return new Interval(new DateTime(this.start, zone), new DateTime(
					this.end, zone));

		} else
			throw new IllegalStateException("Intervals only valid for absences");
	}

	/**
	 * Time-span of an absence
	 */
	public Interval getInterval(Chronology chron) {
		if (getType().isAbsence()) {
			return new Interval(new DateTime(this.start, chron), new DateTime(
					this.end, chron));

		} else
			throw new IllegalStateException("Intervals only valid for absences");
	}

	@Deprecated
	public Date getStart() {
		return start;
	}

	public Status getStatus() {
		return this.status;
	}

	public User getStudent() {
		return student;
	}

	public Type getType() {
		return this.type;
	}

	public void setCheckin(DateTime datetime) {
		if (getType().isTardy())
			this.start = datetime.toDate();
		else
			throw new IllegalStateException("Check-in times only valid for tardies");

	}

	public void setCheckout(DateTime datetime) {
		if (getType().isEarlyCheckOut())
			this.start = datetime.toDate();
		else
			throw new IllegalStateException("Checkout times only valid for early check outs");

	}

	public void setEvent(Event event) {
		this.event = event;
	}

	public void setInterval(Interval interval) {
		if (getType().isAbsence()) {
			this.end = interval.getEnd().toDate();
			this.start = interval.getStart().toDate();
		} else
			throw new IllegalStateException("Intervals only valid for absences");

	}

	public void setStatus(Status status) {
		this.status = status;
	}

	public void setStudent(User student) {
		this.student = student;
	}

	public void setType(Type type) {
		this.type = type;
	}

	@Override
	public String toString() {
		return student.toString() + " Start: " + start.toString() + " End: "
				+ end.toString();
	}

	public static long parseKey(String keyStr) {
		return Long.parseLong(keyStr);
	}
} @Entity(kind = "Event", allocateIdsBy = 0)
public class Event {

	public enum Type {
		Rehearsal, Performance;
		private String mDisplayString;

		private Type() {
			mDisplayString = this.toString();
		}

		private Type(String display_string) {
			mDisplayString = display_string;
		}

		public String getDisplayName() {
			return mDisplayString;
		}

		public String getValue() {
			return name();
		}

		public boolean isPerformance() {
			return Performance.equals(this);
		}

		public boolean isRehearsal() {
			return Rehearsal.equals(this);
		}

	}

	public static final String FIELD_START = "start";
	public static final String FIELD_END = "end";

	public static final String FIELD_TYPE = "type";

	@Id
	private long id;

	@Index
	private Date start;

	@Index
	private Date end;

	private Type type;

	/**
	 * No-args constructor for datastore
	 */
	Event() {

	}

	@Deprecated
	public Date getDate() {
		return start;
	}

	@Deprecated
	public Date getEnd() {
		return end;
	}

	public long getId() {
		return this.id;
	}

	public Interval getInterval(DateTimeZone zone) {
		return new Interval(new DateTime(start, zone), new DateTime(end, zone));
	}

	@Deprecated
	public Date getStart() {
		return start;
	}

	public Type getType() {
		return this.type;
	}

	public void setInterval(Interval interval) {
		this.start = interval.getStart().toDate();
		this.end = interval.getEnd().toDate();
	}

	public void setType(Event.Type type) {
		this.type = type;
	}

	@Override
	public String toString() {
		return "Start: " + start.toString() + " End: " + end.toString();
	}
} @Entity(kind = "User", allocateIdsBy = 0)
public class User implements Serializable {

	public enum Grade {
		A, B, C, D, F;
		private String mDisplayString;

		private Grade() {
			mDisplayString = this.toString();
		}

		private Grade(String display_string) {
			mDisplayString = display_string;
		}

		public String getDisplayName() {
			return mDisplayString;
		}

		public String getValue() {
			return name();
		}
	}

	public enum Section {
		Piccolo, Clarinet, AltoSax("Alto Sax"), TenorSax("Tenor Sax"), Trumpet, Trombone, Bass_Trombone(
				"Bass trombone"), Mellophone, Baritone, Sousaphone, Guard, DrumMajor(
				"Drum Major"), Staff, Drumline_Cymbals("Drumline: Cymbals"), Drumline_Tenors(
				"Drumline: Tenors"), Drumline_Snare("Drumline: Snare"), Drumline_Bass(
				"Drumline: Bass"), Twirler;

		private String mDisplayString;

		private Section() {
			mDisplayString = this.toString();
		}

		private Section(String display_string) {
			mDisplayString = display_string;
		}

		public String getDisplayName() {
			return mDisplayString;
		}

		public String getValue() {
			return name();
		}
	}

	public enum Type {
		Student, TA, Director;

		public boolean isDirector() {
			return this.equals(Director);
		}

		public boolean isStudent() {
			return this.equals(Student);
		}

		public boolean isTa() {
			return this.equals(TA);
		}
	}

	private static final long serialVersionUID = 1421557192976557704L;

	public static final String FIELD_TYPE = "type";

	public static final String FIELD_ID = "id";

	public static final String FIELD_PRIMARY_EMAIL = "email";

	public static final String FIELD_SECONDARY_EMAIL = "secondEmail";

	public static final String FIELD_UNIVERSITY_ID = "universityID";

	private Type type;

	private Grade grade;

	@Id
	private String id;

	private Email email;

	private Email secondEmail;

	private String universityID;

	private Section section;

	private String firstName;

	private String lastName;

	private int year;

	private String major;

	private String rank;

	private boolean showApproved;

	// number of minutes available from submitted TimeWorked forms
	private int minutesAvailable;

	// number of unexcused minutes absent so far this semester
	private int minutesMissed;

	/**
	 * Create users through UserController (DataModel.users().create(...)
	 */
	User() {

	}

	@Override
	public boolean equals(Object o) {
		if (o == null)
			return false;

		// emails are unique, so compare based on them
		if (o instanceof User) {
			User u = (User) o;

			if (this.email == null || this.email.getEmail() == null
					|| u.email == null || u.email.getEmail() == null) {
				if (this.email == null && u.email == null) {
					return true;
				} else if (this.email.getEmail() == null
						&& u.email.getEmail() == null) {
					return true;
				} else {
					return false;
				}
			}

			if (this.email.equals(u.email))
				return true;
		}
		return false;
	}

	public String getFirstName() {
		return firstName;
	}

	public Grade getGrade() {

		return this.grade;
	}

	public String getId() {
		return this.id;
	}

	public String getLastName() {
		return lastName;
	}

	public String getMajor() {
		return major;
	}

	public int getMinutesAvailable() {
		return minutesAvailable;
	}

	public String getName() {
		return this.getFirstName() + " " + this.getLastName();
	}

	/**
	 * Iowa State specific, instead use getId()
	 * 
	 * @return net id of the user
	 */
	@Deprecated
	public String getNetID() {
		return this.id;
	}

	public Email getPrimaryEmail() {
		return this.email;
	}

	public String getRank() {
		return this.rank;
	}

	public Email getSecondaryEmail() {
		return this.secondEmail;
	}

	public Section getSection() {
		return section;
	}

	public Type getType() {
		return this.type;
	}

	public String getUniversityID() {
		return universityID;
	}

	public int getYear() {
		return year;
	}

	@Override
	public int hashCode() {
		if (this.email == null || this.email.getEmail() == null)
			return 0;
		else
			return this.email.hashCode();
	}

	public boolean isShowApproved() {
		return this.showApproved;
	}

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

	public void setGrade(Grade grade) {
		this.grade = grade;
	}

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

	public void setMajor(String major) {
		this.major = major;
	}

	public void setMinutesAvailable(int minutesAvailable) {
		this.minutesAvailable = minutesAvailable;
	}

	public void setPrimaryEmail(Email email) {
		this.email = email;
		// TODO https://github.com/curtisullerich/attendance/issues/170
		// This shouldn't be in the model, it should happen in the controllers
		// somehow
		if (email.getEmail().indexOf('%') == -1) {
			this.id = email.getEmail().substring(0,
					email.getEmail().indexOf('@'));
		} else {
			this.id = email.getEmail().substring(0,
					email.getEmail().indexOf('%'));
		}
	}

	public void setRank(String rank) {
		this.rank = rank;
	}

	public void setSecondaryEmail(Email email) {
		this.secondEmail = email;
	}

	public void setSection(Section section) {
		this.section = section;
	}

	public void setShowApproved(boolean show) {
		this.showApproved = show;
	}

	public void setType(Type type) {
		this.type = type;
	}

	public void setUniversityID(String new_universityID) {
		this.universityID = new_universityID;
	}

	public void setYear(int year) {
		this.year = year;
	}

	public void setMinutesMissed(int minutes) {
		this.minutesMissed = minutes;
	}

	public int getMinutesMissed() {
		return this.minutesMissed;
	}

	@Override
	public String toString() {
		return getName();
	}

} public class MobileAppDataServlet extends AbstractBaseServlet {

	private class ClassListResult {
		@SuppressWarnings("unused")
		public ResultErrorType error;
		@SuppressWarnings("unused")
		public String data;
	}

	private class ClassListResultV2 {
		@SuppressWarnings("unused")
		public ResultErrorType error;
		@SuppressWarnings("unused")
		public List<MobileDataManager.ClassListV2User> data;
	}

	public enum Page {
		index, indexv2;
	}

	private enum ResultErrorType {
		success, login, exception, empty
	}

	private class UploadResult {
		@SuppressWarnings("unused")
		public ResultErrorType error;
		@SuppressWarnings("unused")
		public String message;
	}

	private static final long serialVersionUID = -3138258973922548889L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		Page page = parsePathInfo(req.getPathInfo(), Page.class);
		if (page == null)
			ErrorServlet.showError(req, resp, 404);
		else
			switch (page) {
			case index:
				getIndex(req, resp);
				break;
			case indexv2:
				getIndexV2(req, resp);
				break;
			default:
				ErrorServlet.showError(req, resp, 404);
			}
	}

	private void getIndexV2(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		ClassListResultV2 result = new ClassListResultV2();

		// Check if correct user type is logged in
		if (!isLoggedIn(req, resp, User.Type.TA, User.Type.Director)) {
			result.error = ResultErrorType.login;
		} else {
			DataTrain train = DataTrain.depart();

			MobileDataManager mdc = train.mobileData();

			result.data = mdc.getClassListV2();
			result.error = ResultErrorType.success;
		}

		// Print out JSON of result
		resp.getOutputStream().print(new Gson().toJson(result));
	}

	private void getIndex(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		ClassListResult result = new ClassListResult();

		// Check if correct user type is logged in
		if (!isLoggedIn(req, resp, User.Type.TA, User.Type.Director)) {
			result.error = ResultErrorType.login;
		} else {
			DataTrain train = DataTrain.depart();

			MobileDataManager mdc = train.mobileData();

			result.data = mdc.getClassList();
			result.error = ResultErrorType.success;
		}

		// Print out JSON of result
		resp.getOutputStream().print(new Gson().toJson(result));
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		Page page = parsePathInfo(req.getPathInfo(), Page.class);
		if (page == null)
			ErrorServlet.showError(req, resp, 404);
		else
			switch (page) {
			case index:
				postIndex(req, resp);
				break;
			case indexv2:
				postIndexV2(req, resp);
				break;
			default:
				ErrorServlet.showError(req, resp, 404);
			}
	}

	private void postIndexV2(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {

		UploadResult result = new UploadResult();

		// Check if correct user type is logged in
		if (!isLoggedIn(req, resp, User.Type.TA, User.Type.Director)) {
			result.error = ResultErrorType.login;
		} else {
			DataTrain train = DataTrain.depart();
			MobileDataManager mdc = train.mobileData();
			Reader r = new InputStreamReader(req.getInputStream());

			// Optimistically set a success type
			result.error = ResultErrorType.success;

			// TODO set result.error = ResultErrorType.empty if empty,

			try {
				result.message = mdc.pushMobileDataV2(r, train.auth()
						.getCurrentUser(req.getSession()));
				// TODO these exceptions might be different types for gson
			} catch (IllegalArgumentException e) {
				result.error = ResultErrorType.exception;
				result.message = e.getMessage();
			} catch (IllegalStateException e) {
				result.error = ResultErrorType.exception;
				result.message = e.getMessage();
			}
		}

		// Print out JSON of result
		resp.getOutputStream().print(new Gson().toJson(result));

	}

	private void postIndex(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {

		UploadResult result = new UploadResult();

		// Check if correct user type is logged in
		if (!isLoggedIn(req, resp, User.Type.TA, User.Type.Director)) {
			result.error = ResultErrorType.login;
		} else {

			DataTrain train = DataTrain.depart();

			MobileDataManager mdc = train.mobileData();

			// data here is the posted content delimited by "&newline&". Those
			// values in turn are
			// delimited by "&split&"
			String data;
			try {
				data = new java.util.Scanner(req.getInputStream())
						.useDelimiter("\\A").next();
			} catch (java.util.NoSuchElementException e) {
				data = "";
			}

			if (data.trim().equals("")) {
				// Empty data uploaded
				result.error = ResultErrorType.empty;
			} else {
				// Optimistically set a success type
				result.error = ResultErrorType.success;

				try {
					result.message = mdc.pushMobileData(data, train.auth()
							.getCurrentUser(req.getSession()));
				} catch (IllegalArgumentException e) {
					result.error = ResultErrorType.exception;
					result.message = e.getMessage();
				} catch (IllegalStateException e) {
					result.error = ResultErrorType.exception;
					result.message = e.getMessage();
				}
			}

		}

		// Print out JSON of result
		resp.getOutputStream().print(new Gson().toJson(result));

	}
} @Ignore
public class AbstractDatastoreTest {

	private DataTrain datatrain = null;

	private LocalServiceTestHelper helper = null;
	
	static {
		App.CachingEnabled = false;
	}

	@Before
	public void setUp() {
		helper = new LocalServiceTestHelper(
				new LocalDatastoreServiceTestConfig(),
				new LocalUserServiceTestConfig());
		helper.setUp();
		datatrain = DataTrain.depart();
	}

	@After
	public void tearDown() {
		helper.tearDown();
		datatrain = null;
		helper = null;
	}

	protected final DataTrain getDataTrain() {
		if (datatrain == null) {
			throw new IllegalAccessError(
					"Access data train outside of a test case is not allowed.");
		} else {
			return datatrain;
		}
	}
} public class ServletMocks {

	public static <T extends HttpServlet> void doGet(Class<T> clazz,
			HttpServletRequest req, HttpServletResponse resp)
			throws InstantiationException, IllegalAccessException,
			ServletException, IOException {

		commonSetup(req, resp);

		when(req.getMethod()).thenReturn("GET");

		HttpServlet s = clazz.newInstance();
		s.service(req, resp);
	}

	public static <T extends HttpServlet> void doPost(Class<T> clazz,
			HttpServletRequest req, HttpServletResponse resp)
			throws InstantiationException, IllegalAccessException,
			ServletException, IOException {

		commonSetup(req, resp);

		when(req.getMethod()).thenReturn("POST");

		HttpServlet s = clazz.newInstance();
		s.service(req, resp);
	}

	public static HttpServletRequest setUserSession(HttpServletRequest req, User u) {
		HttpSession session = mock(HttpSession.class);
		when(session.getAttribute("authenticated_user")).thenReturn(u);
		
		when(req.getSession()).thenReturn(session);
		
		return req;
	}

	public static HttpServletRequest setPage(HttpServletRequest req, Enum<?> page) {
		when(req.getPathInfo()).thenReturn("somepath/" + page);
		return req;
	}

	public static void setPostedContent(HttpServletRequest req, String data)
			throws IOException {
		ByteArrayInputStream realStream = new ByteArrayInputStream(
				data.getBytes("UTF-8"));
		ServletInputStream mockStream = new MockServletInputStream(realStream);
		when(req.getInputStream()).thenReturn(mockStream);
	}

	private static void commonSetup(HttpServletRequest req,
			HttpServletResponse resp) {
		when(req.getHeader("User-Agent"))
				.thenReturn(
						"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5");
	}

} @Ignore
public class Config {

	public static String getEmailDomain() {
		return App.DOMAIN;
	}

} @Ignore
public class Users {

	public static final User createDefaultStudent(UserManager uc) {
		return createStudent(uc, "defaultstudent", "123456789", "John", "Doe", 2,
				"major", User.Section.AltoSax);
	}
	
	public static final User createDefaultTA(UserManager uc) {
		return createTA(uc, "defaultta", "987654321", "Johnny", "Doe", 2,
				"major", User.Section.AltoSax);
	}

	public static final User createStudent(UserManager uc,
			String email_firstpart, String univID, String firstName,
			String lastName, int year, String major, User.Section section) {

		com.google.appengine.api.users.User google_user = new com.google.appengine.api.users.User(
				email_firstpart + "@" + Config.getEmailDomain(),
				"gmail.com");

		return uc.createStudent(google_user, univID, firstName, lastName, year,
				major, section, Util.makeEmail(""));
	}

	public static final User createTA(UserManager uc, String email_firstpart,
			String univID, String firstName, String lastName, int year,
			String major, User.Section section) {

		com.google.appengine.api.users.User google_user = new com.google.appengine.api.users.User(
				email_firstpart + "@" + Config.getEmailDomain(),
				"gmail.com");

		User u = uc.createStudent(google_user, univID, firstName, lastName,
				year, major, section, Util.makeEmail(""));
		u.setType(User.Type.TA);
		uc.update(u);
		return u;
	}

	public static final User createDirector(UserManager uc,
			String email_firstpart, String firstName, String lastName) {
		return uc.createDirector(
				email_firstpart + "@" + Config.getEmailDomain(),
				email_firstpart + ".secondemail" + "@"
						+ Config.getEmailDomain(), firstName, lastName);
	}

	public static User createDefaultDirector(UserManager uc) {
		return Users.createDirector(uc, "director", "I am", "The Director");
	}
} public class DateTimeConverter implements JsonSerializer<DateTime>, JsonDeserializer<DateTime>
{
  /**
   * Gson invokes this call-back method during serialization when it encounters a field of the
   * specified type. <p>
   *
   * In the implementation of this call-back method, you should consider invoking
   * {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
   * non-trivial field of the {@code src} object. However, you should never invoke it on the
   * {@code src} object itself since that will cause an infinite loop (Gson will call your
   * call-back method again).
   *
   * @param src the object that needs to be converted to Json.
   * @param typeOfSrc the actual type (fully genericized version) of the source object.
   * @return a JsonElement corresponding to the specified object.
   */
  @Override
  public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context)
  {
    final DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
    return new JsonPrimitive(fmt.print(src));
  }

  /**
   * Gson invokes this call-back method during deserialization when it encounters a field of the
   * specified type. <p>
   *
   * In the implementation of this call-back method, you should consider invoking
   * {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
   * for any non-trivial field of the returned object. However, you should never invoke it on the
   * the same type passing {@code json} since that will cause an infinite loop (Gson will call your
   * call-back method again).
   *
   * @param json The Json data being deserialized
   * @param typeOfT The type of the Object to deserialize to
   * @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
   * @throws JsonParseException if json is not in the expected format of {@code typeOfT}
   */
  @Override
  public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  {
    final DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
    return fmt.parseDateTime(json.getAsString()).toDateTime(DateTimeZone.UTC);
  }
} public class UploadAbsence {

	public Absence.Type type;
	public DateTime time;
	public String netid;
} public class UploadEvent {

	public List<UploadAbsence> absences;
	public Event.Type type;
	public DateTime startDateTime;
	public DateTime endDateTime;
}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import edu.iastate.music.marching.attendance.Lang;
import edu.iastate.music.marching.attendance.model.interact.DataTrain;
import edu.iastate.music.marching.attendance.model.interact.UserManager;
import edu.iastate.music.marching.attendance.model.store.Absence;
import edu.iastate.music.marching.attendance.model.store.Event;
import edu.iastate.music.marching.attendance.model.store.User;
import edu.iastate.music.marching.attendance.servlets.MobileAppDataServlet;
import edu.iastate.music.marching.attendance.testlib.AbstractDatastoreTest;
import edu.iastate.music.marching.attendance.testlib.ServletMocks;
import edu.iastate.music.marching.attendance.testlib.Config;
import edu.iastate.music.marching.attendance.testlib.Users;
import edu.iastate.music.marching.attendance.util.DateTimeConverter;
import edu.iastate.music.marching.attendance.util.UploadAbsence;
import edu.iastate.music.marching.attendance.util.UploadEvent;
package edu.iastate.music.marching.attendance.test.servlets;







public class MobileDataUploadV2Test extends AbstractDatastoreTest {

	private static final String SIMPLE_ABSENCE_TESTDATA_V2 = "[ { \"absences\": [ { \"type\": \"Absence\", \"netid\": \"ehayles\" }, { \"type\": \"Absence\", \"netid\": \"alusk\" } ], \"type\": \"Rehearsal\", \"startDateTime\": \"2012-05-03T16:30:00.000Z\", \"endDateTime\": \"2012-05-03T17:50:00.000Z\" } ]";
	private static final String SIMPLE_TARDY_TESTDATA_V2 = "[ { \"absences\": [ { \"type\": \"Tardy\", \"time\": \"2012-05-03T01:09:00.000Z\", \"netid\": \"ehayles\" }, { \"type\": \"Tardy\", \"time\": \"2012-05-03T01:16:00.000Z\", \"netid\": \"ehayles\" }, { \"type\": \"Tardy\", \"time\": \"2012-05-03T01:08:00.000Z\", \"netid\": \"jbade\" }, { \"type\": \"Tardy\", \"time\": \"2012-05-03T01:07:00.000Z\", \"netid\": \"ehayles\" }, { \"type\": \"Tardy\", \"time\": \"2012-05-03T01:08:00.000Z\", \"netid\": \"ehayles\" }, { \"type\": \"Tardy\", \"time\": \"2012-05-03T01:08:00.000Z\", \"netid\": \"alusk\" } ], \"type\": \"Rehearsal\", \"startDateTime\": \"2012-05-03T16:30:00.000Z\", \"endDateTime\": \"2012-05-03T17:50:00.000Z\" } ]";
	private static final String SIMPLE_SINGLE_TARDY_STRING = "[{\"absences\":[{\"type\":\"Absence\",\"netid\":\"ehayles\"}],\"type\":\"Rehearsal\",\"startDateTime\":\"2012-05-03T16:30:00.000Z\",\"endDateTime\":\"2012-05-03T17:50:00.000Z\"}]";

	public class DateTimeWrapper {
		public DateTime value;
	}

	@Test
	public void deserializeISO8601DateTime_CorrectDateTime() {

		String str = "{value:\"3141-05-09T02:06:05.035Z\"}";
		DateTime expected = new DateTime(3141, 5, 9, 2, 6, 5, 35,
				DateTimeZone.UTC);

		GsonBuilder gsonBuilder = new GsonBuilder();
		gsonBuilder
				.registerTypeAdapter(DateTime.class, new DateTimeConverter());