public interface DatabaseHelper extends Closeable {

    public DatabaseSession getSession();
} public interface DatabaseSession extends Closeable {

    public Cursor rawQuery(String sql, String[] selectionArgs);
    public void execSQL(String sql);
    public int update(String table, ContentValues contentvalues, String whereClause, String[] whereArgs);
    public long replace(String table, String nullColumnHack, ContentValues values);
    public long replaceOrThrow(String table, String nullColumnHack, ContentValues values);
} public class FeatureToggle {

    private SharedPreferences sharedPreferences;

    @Inject
    public FeatureToggle(SharedPreferences sharedPreferences) {
        this.sharedPreferences = sharedPreferences;
    }

    public boolean isEnabled(FEATURE feature) throws JSONException {
        JSONObject features = new JSONObject(sharedPreferences.getString("features", "{}"));
        if (features.optBoolean("Enquiries", true)) {
            return true;
        }
        return false;
    }
} public class Enquiry extends BaseModel {

    public static final String ENQUIRY_FORM_NAME = "Enquiries";
    public static final String FIELD_ATTACHMENTS = "_attachments";

    public Enquiry() throws JSONException {
        super();
        this.setUniqueId(createUniqueId());
    }

    public Enquiry(String content, String createdBy) throws JSONException {
        super(content);
        this.setCreatedBy(createdBy);
        this.setLastUpdatedAt(RapidFtrDateTime.now().defaultFormat());
    }

    public Enquiry(Cursor cursor) throws JSONException {
        super(cursor.getString(cursor.getColumnIndex(content.getColumnName())));

        for (Database.EnquiryTableColumn column : Database.EnquiryTableColumn.values()) {
            final int columnIndex = cursor.getColumnIndex(column.getColumnName());

            if (columnIndex < 0 || column.equals(content)) {
                continue;
            } else if (column.getPrimitiveType().equals(Boolean.class)) {
                this.put(column.getColumnName(), cursor.getInt(columnIndex) == 1);
            } else {
                this.put(column.getColumnName(), cursor.getString(columnIndex));
            }
        }
    }

    public Enquiry(String enquiryJSON) throws JSONException {
        super(enquiryJSON);
        setHistories();
    }

    public boolean isValid() {
        int numberOfInternalFields = names().length();

        for (Database.EnquiryTableColumn field : Database.EnquiryTableColumn.internalFields()) {
            if (has(field.getColumnName())) {
                numberOfInternalFields--;
            }
        }
        return numberOfInternalFields > 0;
    }

    public JSONObject values() throws JSONException {
        List<Object> names = asList(names());

        Iterable<Object> systemFields = Iterables.transform(Database.EnquiryTableColumn.systemFields(), new Function<Database.EnquiryTableColumn, Object>() {
            @Override
            public Object apply(Database.EnquiryTableColumn enquiryTableColumn) {
                return enquiryTableColumn.getColumnName();
            }
        });

        Iterables.removeAll(names, Lists.newArrayList(systemFields));
        return new JSONObject(this, names.toArray(new String[names.size()]));
    }


    @Override
    public List<BaseModel> getConfirmedMatchingModels(PotentialMatchRepository potentialMatchRepository, ChildRepository childRepository, EnquiryRepository enquiryRepository) {
        return getMatchesByConfirmationStatus(potentialMatchRepository, childRepository, true);
    }

    @Override
    public List<BaseModel> getPotentialMatchingModels(PotentialMatchRepository potentialMatchRepo, ChildRepository childRepository, EnquiryRepository enquiryRepository) throws JSONException {
        return getMatchesByConfirmationStatus(potentialMatchRepo, childRepository, false);
    }

    private List<BaseModel> getMatchesByConfirmationStatus(PotentialMatchRepository potentialMatchRepo, ChildRepository childRepository, boolean status) {
        List<BaseModel> models = new ArrayList<BaseModel>();
        try {
            List<PotentialMatch> matches = potentialMatchRepo.getPotentialMatchesFor(this);
            Collection<PotentialMatch> potentialMatches = Collections2.filter(matches, new PotentialMatch.FilterByConfirmationStatus(status));
            models.addAll(childRepository.getAllWithInternalIds(idsFromMatches(potentialMatches)));
            return models;
        } catch (JSONException exception) {
            return new ArrayList<BaseModel>();
        }
    }

    public static List<String> idsFromMatches(Collection<PotentialMatch> potentialMatches) {
        List<String> ids = new ArrayList<String>();
        for (PotentialMatch potentialMatch : potentialMatches) {
            ids.add(potentialMatch.getChildId());
        }
        return ids;
    }

    @Override
    public String getApiPath() {
        return "/api/enquiries";
    }

    @Override
    public String getApiParameter() {
        return "enquiry";
    }

    @Override
    public String getRecordedAudio() {
        if(has("recorded_audio"))
            return optString("recorded_audio");
        return optString("7_recorded_audio"); //Due to hard coded form section field ids in the Rails app...
    }
} public class PotentialMatch extends BaseModel {

    public static enum PotentialMatchStatus {
        POTENTIAL,
        DELETED,
        INVALID,
        CONFIRMED,
        REUNITED,
        REUNITED_ELSEWHERE
    }

    private static final String ENQUIRY_ID_FIELD = "enquiry_id";
    private static final String CHILD_ID_FIELD = "child_id";
    public static final String STATUS_FIELD = "status";

    public PotentialMatch(String jsonString) throws JSONException {
        super(jsonString);
    }

    public PotentialMatch(String enquiryId, String childId, String uniqueIdentifier) {
        this.put(ENQUIRY_ID_FIELD, enquiryId);
        this.put(CHILD_ID_FIELD, childId);
        this.put(FIELD_INTERNAL_ID, uniqueIdentifier);
        this.put(STATUS_FIELD, PotentialMatchStatus.POTENTIAL.name());
    }

    public PotentialMatch(String enquiryId, String childId, String uniqueIdentifier, Boolean isConfirmed) {
        this(enquiryId, childId, uniqueIdentifier);

        this.put(STATUS_FIELD, isConfirmed ? PotentialMatchStatus.CONFIRMED.name() : PotentialMatchStatus.POTENTIAL.name());
    }

    public String getChildId() {
        return getString(CHILD_ID_FIELD);
    }

    public String getEnquiryId() {
        return getString(ENQUIRY_ID_FIELD);
    }

    public String getUniqueId() {
        return getString(FIELD_INTERNAL_ID);
    }

    public String getRevision() {
        return getString(FIELD_REVISION_ID);
    }

    @Override
    public String getApiPath() {
        return "/api/potential_matches";
    }

    @Override
    public String getApiParameter() {
        return "potential_match";
    }

    public Boolean isConfirmed() {
        return PotentialMatchStatus.valueOf(getString(STATUS_FIELD)) == PotentialMatchStatus.CONFIRMED;
    }

    public Boolean isDeleted() {
        return PotentialMatchStatus.valueOf(getString(STATUS_FIELD)) == PotentialMatchStatus.DELETED;
    }

    public static class FilterByConfirmationStatus implements Predicate<PotentialMatch> {
        private Boolean keepConfirmedMatches;

        public FilterByConfirmationStatus(Boolean keepConfirmedMatches) {
            this.keepConfirmedMatches = keepConfirmedMatches;
        }

        @Override
        public boolean apply(PotentialMatch potentialMatch) {
            return potentialMatch.isConfirmed() == keepConfirmedMatches;
        }
    }
} @JsonIgnoreProperties(ignoreUnknown = true)
@EqualsAndHashCode(of = {"userName", "verified"})
@AllArgsConstructor(suppressConstructorProperties = true)
public class User {

	public static final ObjectMapper JSON_MAPPER = new ObjectMapper();
	public static final String UNAUTHENTICATED_DB_KEY = "UNAUTHENTICATED_DB_KEY";
	public static final String UNAUTHENTICATED_DB_NAME = "DB-" + UNAUTHENTICATED_DB_KEY.hashCode();

	@Getter @JsonProperty("user_name")
	protected String userName;

	@Getter @Setter @JsonIgnore
	protected String password;

    @Getter @Setter @JsonProperty("verified")
	protected boolean verified;

	@Getter @Setter @JsonProperty("server_url")
	protected String serverUrl;

	@Getter @Setter @JsonProperty("db_key")
	protected String dbKey;

	@Getter @Setter @JsonProperty("organisation")
	protected String organisation;

	@Getter @Setter @JsonProperty("full_name")
	protected String fullName;

	@Getter @Setter @JsonProperty("unauthenticated_password")
	protected String unauthenticatedPassword;

	@Getter @Setter @JsonProperty("language")
	protected String language;

	protected User() {
	}

	public User(String userName) {
		this(userName, null, false, null, null, null, null, null, null);
	}

	public User(String userName, String password) {
		this(userName, password, false, null, null, null, null, null, null);
	}

	public User(String userName, String password, boolean authenticated) {
		this(userName, password, authenticated, null, null, null, null, null, null);
	}

	public User(String userName, String password, boolean authenticated, String serverUrl) {
		this(userName, password, authenticated, serverUrl, null, null, null, null, null);
	}

	public String getDbName() {
		return getDbKey() == null ? null : ("DB-" + getDbKey().hashCode());
	}

	public String asJSON() throws IOException {
		return getJsonMapper().writeValueAsString(this);
	}

	public String asEncryptedJSON() throws IOException, GeneralSecurityException {
		if (this.password == null)
			throw new GeneralSecurityException("User password not available to encrypt");
		else
			return EncryptionUtil.encrypt(this.password, this.userName, this.asJSON());
	}

	public User read(String json) throws IOException {
		getJsonMapper().readerForUpdating(this).readValue(json);
		return this;
	}

	public User load() throws GeneralSecurityException, IOException {
		String encryptedJson = getSharedPreferences().getString(prefKey(userName), null);
		String json = EncryptionUtil.decrypt(password, this.userName, encryptedJson);
		return read(json);
	}

	public void save() throws IOException, GeneralSecurityException {
		if (!this.isVerified()) {
			this.setUnauthenticatedPassword(this.getPassword());
			this.setDbKey(getUnauthenticatedDbKey());
		}

		getSharedPreferences().edit().putString(prefKey(userName), asEncryptedJSON()).commit();
	}

	public boolean exists() {
		return getSharedPreferences().contains(prefKey(userName));
	}

	protected ObjectMapper getJsonMapper() {
		return JSON_MAPPER;
	}

	public static User readFromJSON(String json) throws IOException {
		return new User().read(json);
	}

	protected static SharedPreferences getSharedPreferences() {
		return RapidFtrApplication.getApplicationInstance().getSharedPreferences();
	}

	protected static String getUnauthenticatedDbKey() {
		String dbKey = getSharedPreferences().getString(UNAUTHENTICATED_DB_KEY, null);
		return dbKey != null ? dbKey : createUnauthenticatedDbKey();
	}

	protected static String createUnauthenticatedDbKey() {
		String dbKey = UUID.randomUUID().toString();
		getSharedPreferences().edit().putString(UNAUTHENTICATED_DB_KEY, dbKey).commit();
		return dbKey;
	}

	protected static String prefKey(String userName) {
		return "user_" + userName;
	}

} public class EnquiryRepository implements Closeable, Repository<Enquiry> {

    private final String userName;
    private final DatabaseSession session;
    private final RapidFtrApplication applicationInstance;

    @Inject
    public EnquiryRepository(@Named("USER_NAME") String userName, DatabaseSession session, RapidFtrApplication rapidFtrApplication) {
        this.userName = userName;
        this.session = session;
        this.applicationInstance = rapidFtrApplication;
    }

    @Override
    public void createOrUpdate(Enquiry enquiry) throws JSONException {
        if (exists(enquiry.getUniqueId())) {
            Enquiry existingEnquiry = get(enquiry.getUniqueId());
            enquiry.addHistory(History.buildHistoryBetween(applicationInstance, existingEnquiry, enquiry));
        } else {
            User currentUser = RapidFtrApplication.getApplicationInstance().getCurrentUser();
            enquiry.addHistory(History.buildCreationHistory(enquiry, currentUser));
        }
        enquiry.setLastUpdatedAt(RapidFtrDateTime.now().defaultFormat());
        createOrUpdateWithoutHistory(enquiry);
    }

    @Override
    public void createOrUpdateWithoutHistory(Enquiry enquiry) throws JSONException {
        session.replaceOrThrow(Database.enquiry.getTableName(), null, getContentValuesFrom(enquiry));
    }

    protected ContentValues getContentValuesFrom(Enquiry enquiry) throws JSONException {
        ContentValues enquiryValues = new ContentValues();

        enquiryValues.put(id.getColumnName(), enquiry.getUniqueId());
        enquiryValues.put(created_by.getColumnName(), enquiry.getCreatedBy());
        enquiryValues.put(content.getColumnName(), enquiry.getJsonString());
        enquiryValues.put(created_at.getColumnName(), enquiry.getCreatedAt());
        enquiryValues.put(unique_identifier.getColumnName(), enquiry.getUniqueId());
        enquiryValues.put(synced.getColumnName(), enquiry.isSynced());
        enquiryValues.put(internal_id.getColumnName(), enquiry.optString(internal_id.getColumnName()));
        enquiryValues.put(internal_rev.getColumnName(), enquiry.optString(internal_rev.getColumnName()));

        return enquiryValues;
    }

    @Override
    public HashMap<String, String> getAllIdsAndRevs() throws JSONException {
        HashMap<String, String> idRevs = new HashMap<String, String>();
        @Cleanup Cursor cursor = session.rawQuery("SELECT "
                + internal_id.getColumnName() + ", "
                + internal_rev.getColumnName()
                + " FROM " + enquiry.getTableName(), null);
        while (cursor.moveToNext()) {
            idRevs.put(cursor.getString(0), cursor.getString(1));
        }
        return idRevs;
    }

    public List<Enquiry> allCreatedByCurrentUser() throws JSONException {
        @Cleanup Cursor cursor = session.rawQuery("SELECT enquiry_json FROM enquiry WHERE created_by = ? ORDER BY id", new String[]{userName});
        return toEnquiries(cursor);
    }

    @Override
    public List<Enquiry> currentUsersUnsyncedRecords() throws JSONException {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<String> getRecordIdsByOwner() throws JSONException {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<Enquiry> toBeSynced() throws JSONException {
        @Cleanup Cursor cursor = session.rawQuery("SELECT * FROM enquiry WHERE synced" +
                " = ?", new String[]{falseValue.getColumnValue()});
        return toEnquiries(cursor);
    }

    @Override
    public boolean exists(String id) {
        @Cleanup Cursor cursor = session.rawQuery("SELECT * FROM enquiry WHERE id = ?", new String[]{id == null ? "" : id});
        return cursor.moveToNext() && cursor.getCount() > 0;
    }

    @Override
    public int size() {
        @Cleanup Cursor cursor = session.rawQuery("SELECT COUNT(1) FROM enquiry", new String[]{});
        return cursor.moveToNext() ? cursor.getInt(0) : 0;
    }

    @Override
    public void close() {
        try {
            session.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public List<Enquiry> all() throws JSONException {
        @Cleanup Cursor cursor = session.rawQuery("SELECT * FROM enquiry", new String[]{});
        return toEnquiries(cursor);
    }

    List<Enquiry> toEnquiries(Cursor cursor) throws JSONException {
        List<Enquiry> enquiries = new ArrayList<Enquiry>();
        while (cursor.moveToNext()) {
            enquiries.add(buildEnquiry(cursor));
        }
        return enquiries;
    }

    //TODO move this to the enquiry class
    private Enquiry buildEnquiry(Cursor cursor) throws JSONException {
        int contentColumnIndex = cursor.getColumnIndex(content.getColumnName());
        Enquiry enquiry = new Enquiry(cursor.getString(contentColumnIndex));
        for (Database.EnquiryTableColumn column : Database.EnquiryTableColumn.values()) {
            final int columnIndex = cursor.getColumnIndex(column.getColumnName());

            if (columnIndex < 0 || column.equals(content)) {
                continue;
            } else if (column.getPrimitiveType().equals(Boolean.class)) {
                enquiry.put(column.getColumnName(), cursor.getInt(columnIndex) == 1);
            } else {
                enquiry.put(column.getColumnName(), cursor.getString(columnIndex));
            }
        }

        return enquiry;
    }

    public Enquiry get(String enquiryId) throws JSONException {
        @Cleanup Cursor cursor = session.rawQuery("SELECT * from enquiry where id = ?", new String[]{enquiryId});
        if (cursor.moveToNext()) {
            return new Enquiry(cursor);
        } else {
            throw new NullPointerException(enquiryId);  //  I don't think it's cool to throw NullPointerExceptions - love John
        }
    }

    public List<Enquiry> getAllWithInternalIds(List<String> ids) {
        @Cleanup Cursor cursor = session.rawQuery(buildSelectAllQuery(ids), null);
        try {
            return toEnquiries(cursor);
        } catch (JSONException e) {
            return new ArrayList<Enquiry>();
        }
    }

    @Override
    public List<Enquiry> getRecordsForFirstPage() throws JSONException {
        String sql = String.format(
                "SELECT enquiry_json, synced FROM enquiry WHERE created_by ='%s' ORDER BY id LIMIT %d",
                userName, ViewAllChildrenPaginatedScrollListener.FIRST_PAGE);
        @Cleanup Cursor cursor = session.rawQuery(sql, null);
        return toEnquiries(cursor);
    }

    @Override
    public List<Enquiry> getRecordsBetween(int fromPageNumber, int pageNumber) throws JSONException {
        String sql = String.format(
                "SELECT enquiry_json, synced FROM enquiry WHERE created_by='%s' ORDER BY id LIMIT %d OFFSET %d",
                userName, pageNumber - fromPageNumber, pageNumber);
        Log.d("QUERY LIMIT", sql);
        @Cleanup Cursor cursor = session.rawQuery(sql, null);
        return toEnquiries(cursor);
    }

    private String buildSelectAllQuery(List<String> ids) {
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append("SELECT * from enquiry where _id in (");
        for(int i = 0; i < ids.size(); i++) {
            queryBuilder.append("'" + ids.get(i) + "'");
            if(i < ids.size() - 1) {
                queryBuilder.append(",");
            }
        }
        queryBuilder.append(")");
        return queryBuilder.toString();
    }
} public class PotentialMatchRepository implements Closeable, Repository<PotentialMatch> {

    private final String userName;
    private final DatabaseSession session;

    @Inject
    public PotentialMatchRepository(@Named("USER_NAME") String userName, DatabaseSession session) {
        this.userName = userName;
        this.session = session;
    }

    @Override
    public void close() {
        try {
            session.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<PotentialMatch> toBeSynced() throws JSONException {
        return new ArrayList<PotentialMatch>();
    }

    @Override
    public boolean exists(String id) {
        String dbId = (id == null ? "" : id);
        @Cleanup Cursor cursor = session.rawQuery("SELECT * FROM potential_match WHERE id = ?", new String[]{dbId});
        return cursor.moveToNext() && cursor.getCount() > 0;
    }

    @Override
    public PotentialMatch get(String id) throws JSONException {
        @Cleanup Cursor cursor = session.rawQuery("SELECT * FROM potential_match WHERE id = ?", new String[]{id});
        if (cursor.moveToNext()) {
            return buildPotentialMatch(cursor);
        } else {
            return null;
        }
    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public void createOrUpdate(PotentialMatch potentialMatch) throws JSONException, SQLException {
        createOrUpdateWithoutHistory(potentialMatch);
    }

    @Override
    public HashMap<String, String> getAllIdsAndRevs() throws JSONException {
        return new HashMap<String, String>();
    }

    @Override
    public void createOrUpdateWithoutHistory(PotentialMatch potentialMatch) throws JSONException {
        if (potentialMatch.isDeleted()) {
            delete(potentialMatch);
        } else {
            ContentValues values = new ContentValues();
            values.put(enquiry_id.getColumnName(), potentialMatch.getEnquiryId());
            values.put(child_id.getColumnName(), potentialMatch.getChildId());
            values.put(created_at.getColumnName(), potentialMatch.getCreatedAt());
            values.put(id.getColumnName(), potentialMatch.getUniqueId());
            values.put(revision.getColumnName(), potentialMatch.getRevision());
            values.put(confirmed.getColumnName(), potentialMatch.isConfirmed().toString());

            session.replaceOrThrow(Database.potential_match.getTableName(), null, values);
        }
    }

    public void delete(PotentialMatch potentialMatch) {
        session.execSQL(String.format("DELETE FROM potential_match WHERE id ='%s'", potentialMatch.getUniqueId()));
    }

    @Override
    public List<PotentialMatch> currentUsersUnsyncedRecords() throws JSONException {
        return new ArrayList<PotentialMatch>();
    }

    @Override
    public List<String> getRecordIdsByOwner() throws JSONException {
        return new ArrayList<String>();
    }

    @Override
    public List<PotentialMatch> allCreatedByCurrentUser() throws JSONException {
        return new ArrayList<PotentialMatch>();
    }

    @Override
    public List<PotentialMatch> getRecordsBetween(int previousPageNumber, int pageNumber) {
        return null;
    }

    @Override
    public List<PotentialMatch> getRecordsForFirstPage() throws JSONException {
        return null;
    }

    public List<PotentialMatch> getPotentialMatchesFor(Enquiry enquiry) throws JSONException {
        if (enquiry.getInternalId() == null) {
            return new ArrayList<PotentialMatch>();
        }
        @Cleanup Cursor cursor = session.rawQuery("SELECT * FROM potential_match WHERE enquiry_id = ?", new String[]{enquiry.getInternalId()});
        return buildPotentialMatches(cursor);
    }

    public List<PotentialMatch> getPotentialMatchesFor(Child child) throws JSONException {
        if (child.getInternalId() == null) {
            return new ArrayList<PotentialMatch>();
        }
        @Cleanup Cursor cursor = session.rawQuery("SELECT * FROM potential_match WHERE child_id = ?", new String[]{child.getInternalId()});
        return buildPotentialMatches(cursor);
    }

    private ArrayList<PotentialMatch> buildPotentialMatches(Cursor cursor) {
        ArrayList<PotentialMatch> potentialMatches = new ArrayList<PotentialMatch>();
        while (cursor.moveToNext()) {
            potentialMatches.add(buildPotentialMatch(cursor));
        }
        return potentialMatches;
    }

    private PotentialMatch buildPotentialMatch(Cursor cursor) {
        int enquiryIdIndex = cursor.getColumnIndex(enquiry_id.getColumnName());
        int childIdIndex = cursor.getColumnIndex(child_id.getColumnName());
        int idIndex = cursor.getColumnIndex(id.getColumnName());
        int confirmedIndex = cursor.getColumnIndex(confirmed.getColumnName());
        return new PotentialMatch(cursor.getString(enquiryIdIndex), cursor.getString(childIdIndex), cursor.getString(idIndex),
                Boolean.valueOf(cursor.getString(confirmedIndex)));
    }
} public class SyncUnverifiedDataAsyncTask<T extends BaseModel> extends SynchronisationAsyncTask<T> {

    private LoginService loginService;
    private RegisterUserService registerUserService;
    private RapidFtrApplication applicationContext;

    @Inject
    public SyncUnverifiedDataAsyncTask(FormService formService,
                                       SyncService<T> recordService,
                                       Repository<T> repository,
                                       LoginService loginService,
                                       RegisterUserService registerUserService,
                                       User user) {

        super(formService, recordService, repository, user);
        this.loginService = loginService;
        this.registerUserService = registerUserService;
    }

    @Override
    public void setContext(RapidFtrActivity context) {
        this.context = context;
        this.applicationContext = context.getContext();
    }

    protected void sync() throws JSONException, IOException, HttpException {
        setProgressAndNotify(context.getString(R.string.synchronize_step_1), 0);
        registerUser();
        JSONObject response = login();
        RapidFtrApplication application = RapidFtrApplication.getApplicationInstance();
        if(response != null && response.optBoolean("verified") && !application.getCurrentUser().isVerified()){
            startMigrationTask(response, application);
        }
        getFormSections();
        sendRecordsToServer(repository.currentUsersUnsyncedRecords());
        List<String> idsToDownload = recordSyncService.getIdsToDownload();
        List<T> recordsToSyncWithServer = repository.toBeSynced();

        if(!application.getCurrentUser().isVerified()) {
            List<String> idsOfCurrentUser = repository.getRecordIdsByOwner();
            Iterator<String> idIterator = idsToDownload.iterator();
            while (idIterator.hasNext()) {
                String id = idIterator.next();
                if(!idsOfCurrentUser.contains(id)) {
                    idIterator.remove();
                }
            }
        }
        int startProgressForDownloadingChildren = formSectionProgress + recordsToSyncWithServer.size();
        saveIncomingRecords(idsToDownload, startProgressForDownloadingChildren);
        setProgressAndNotify(context.getString(R.string.sync_complete), maxProgress);
    }

    protected void startMigrationTask(JSONObject response, RapidFtrApplication application) {
        new MigrateUnverifiedDataToVerified(response, application.getCurrentUser(), applicationContext).execute();
    }

    private void registerUser() throws IOException {
        String serverUrl = applicationContext.getSharedPreferences().getString(SERVER_URL_PREF, null);
        HttpResponse response = registerUserService.register(currentUser);
        if (response.getStatusLine().getStatusCode() == SC_OK) {
            currentUser.setServerUrl(serverUrl);
        } else {
            applicationContext.getSharedPreferences().edit().putString(SERVER_URL_PREF, "").commit();
        }
    }

    private JSONObject login() throws IOException, JSONException {
        HttpResponse response = loginService.login(applicationContext, currentUser.getUserName(), currentUser.getUnauthenticatedPassword(), currentUser.getServerUrl());
        if(response !=null && (response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED || response.getStatusLine().getStatusCode() == HttpStatus.SC_OK))
            return new JSONObject(getResponse(response));
        return null;
    }

    protected String getResponse(HttpResponse response) throws IOException {
        return CharStreams.toString(new InputStreamReader(response.getEntity().getContent()));
    }

} public abstract class SynchronisationAsyncTask<T extends BaseModel> extends AsyncTask<Object, String, Boolean> {

    public static int NOTIFICATION_ID = 1010 + new Random().nextInt(10);
    private static final String SYNC_ALL = "SYNC_ALL";
    private static final String CANCEL_SYNC_ALL = "CANCEL_SYNC_ALL";

    protected FormService formService;
    protected SyncService<T> recordSyncService;
    protected Repository<T> repository;
    protected User currentUser;
    protected RapidFtrActivity context;
    protected Notification notification;
    protected NotificationManager notificationManager;

    protected int formSectionProgress;
    protected int maxProgress;
    @Setter
    private String successMessage = "Records Successfully Synchronized";

    public SynchronisationAsyncTask(FormService formService, SyncService<T> recordSyncService, Repository<T> repository, User user) {
        this.formService = formService;
        this.recordSyncService = recordSyncService;
        this.repository = repository;
        currentUser = user;
    }

    @Override
    protected void onPreExecute() {
        RapidFtrApplication.getApplicationInstance().setSyncTask(this);
        toggleMenu(CANCEL_SYNC_ALL);
    }

    @Override
    protected Boolean doInBackground(Object... notRelevant) {
        try {
            sync();
            return true;
        } catch (HttpException e) {
            Log.e("SyncAllDataTask", "HTTPError in sync", e);

            String message = RapidFtrApplication.getApplicationInstance().getString(R.string.session_timeout);
            if (e.getMessage() != null && e.getMessage().trim().length() > 0) {
                message = e.getMessage();
            }

            publishProgress(message);
            return false;
        } catch (Exception e) {
            Log.e("SyncAllDataTask", "Error in sync", e);
            publishProgress(e.getMessage());
            return false;
        }
    }

    protected abstract void sync() throws JSONException, IOException, HttpException;

    protected void getFormSections() throws IOException {
        if (!isCancelled()) {
            formService.downloadPublishedFormSections();
        }
    }

    void setProgressBarParameters(List<String> idsToDownload, List<?> recordsToSyncWithServer) {
        int totalRecordsToSynchronize = idsToDownload.size() + recordsToSyncWithServer.size();
        formSectionProgress = totalRecordsToSynchronize / 4 == 0 ? 20 : totalRecordsToSynchronize / 4;
        maxProgress = totalRecordsToSynchronize + formSectionProgress;
    }

    @Override
    protected void onProgressUpdate(String... values) {
        RapidFtrApplication.getApplicationInstance().showNotification(recordSyncService.getNotificationId(),
                recordSyncService.getNotificationTitle(), values[0]);
    }

    @Override
    protected void onPostExecute(Boolean result) {
        toggleMenu(SYNC_ALL);
        RapidFtrApplication.getApplicationInstance().setSyncTask(null);
        if (result) {
            RapidFtrApplication.getApplicationInstance().showNotification(recordSyncService.getNotificationId(),
                    recordSyncService.getNotificationTitle(),
                    successMessage);
            Toast.makeText(RapidFtrApplication.getApplicationInstance(), successMessage, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(RapidFtrApplication.getApplicationInstance(),
                    RapidFtrApplication.getApplicationInstance().getString(R.string.sync_error), Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onCancelled() {
        toggleMenu(SYNC_ALL);
        RapidFtrApplication.getApplicationInstance().cancelNotification(recordSyncService.getNotificationId());
        RapidFtrApplication.getApplicationInstance().setSyncTask(null);
    }

    private void toggleMenu(String showMenu) {
        context.getMenu().getItem(0).setVisible(showMenu.equals(SYNC_ALL));
        context.getMenu().getItem(1).setVisible(showMenu.equals(CANCEL_SYNC_ALL));
    }

    protected void setProgressAndNotify(String statusText, int progress) {
        if (!isCancelled()) {
            RapidFtrApplication.getApplicationInstance().showProgressNotification(recordSyncService.getNotificationId(),
                    context.getString(R.string.sync_title), statusText, maxProgress, progress, false);
        }
    }

    void sendRecordsToServer(List<T> recordsToSyncWithServer) throws IOException, JSONException, HttpException {
        setProgressAndNotify(context.getString(R.string.synchronize_step_2), formSectionProgress);
        String subStatusFormat = "Uploading Record %s of " + recordsToSyncWithServer.size();
        int counter = 0;
        int startProgress = formSectionProgress;
        for (T baseModel : recordsToSyncWithServer) {
            if (isCancelled()) {
                break;
            }
            recordSyncService.sync(baseModel, currentUser);
            setProgressAndNotify(String.format(subStatusFormat, ++counter), startProgress);
            startProgress += 1;
        }
    }

    protected void saveIncomingRecords(List<String> idsToDownload, int startProgress) throws IOException, JSONException, HttpException {
        String subStatusFormat = "Downloading Record %s of" + idsToDownload.size();
        int counter = 0;
        setProgressAndNotify(context.getString(R.string.synchronize_step_3), startProgress);

        for (String idToDownload : idsToDownload) {
            T incomingRecord = recordSyncService.getRecord(idToDownload);
            if (isCancelled()) {
                break;
            }
            try {
                repository.createOrUpdateWithoutHistory(incomingRecord);
                recordSyncService.setMedia(incomingRecord);
                recordSyncService.setLastSyncedAt(incomingRecord);
                setProgressAndNotify(String.format(subStatusFormat, ++counter), startProgress);
                startProgress += 1;
            } catch (Exception e) {
                Log.e("SyncAllDataTask", "Error syncing record", e);
                throw new RuntimeException(e);
            }
        }
    }

    public void setContext(RapidFtrActivity context) {
        this.context = context;
    }
}
import android.content.Context;
import android.content.SharedPreferences;
import com.google.inject.AbstractModule;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.rapidftr.RapidFtrApplication;
import com.rapidftr.database.DatabaseHelper;
import com.rapidftr.database.DatabaseSession;
import com.rapidftr.database.SQLCipherHelper;
import com.rapidftr.features.FeatureToggle;
import com.rapidftr.model.Child;
import com.rapidftr.model.Enquiry;
import com.rapidftr.model.PotentialMatch;
import com.rapidftr.model.User;
import com.rapidftr.repository.ChildRepository;
import com.rapidftr.repository.EnquiryRepository;
import com.rapidftr.repository.PotentialMatchRepository;
import com.rapidftr.repository.Repository;
import com.rapidftr.service.*;
import com.rapidftr.task.RegisterUnverifiedUserAsyncTask;
import com.rapidftr.task.SyncAllDataAsyncTask;
import com.rapidftr.task.SyncUnverifiedDataAsyncTask;
import com.rapidftr.task.SynchronisationAsyncTask;
import com.rapidftr.utils.http.FluentRequest;
import org.json.JSONException;
package com.rapidftr.utils;


public class ApplicationInjector extends AbstractModule {

    @Override
    protected void configure() {
        bind(Context.class).to(RapidFtrApplication.class);
        bind(DatabaseHelper.class).to(SQLCipherHelper.class);
        bind(new TypeLiteral<Repository<Child>>() {
        }).to(ChildRepository.class);
        bind(new TypeLiteral<Repository<Enquiry>>() {
        }).to(EnquiryRepository.class);
        bind(new TypeLiteral<Repository<PotentialMatch>>() {
        }).to(PotentialMatchRepository.class);
        bind(FormService.class);
        bind(RegisterUserService.class);
        bind(RegisterUnverifiedUserAsyncTask.class);
        bind(FluentRequest.class);
        bind(new TypeLiteral<SyncService<Child>>() {
        }).to(ChildSyncService.class);
        bind(new TypeLiteral<SyncService<Enquiry>>() {
        }).to(EnquirySyncService.class);
        bind(new TypeLiteral<SyncService<PotentialMatch>>() {
        }).to(PotentialMatchSyncService.class);

        bind(LogOutService.class);
        bind(LoginService.class);
        bind(DeviceService.class);
        bind(FeatureToggle.class);
    }

    @Provides
    @Named("USER_NAME")
    public String getUserName(User user) {
        return user.getUserName();
    }

    @Provides
    public RapidFtrApplication getRapidFTRApplication() {
        return RapidFtrApplication.getApplicationInstance();
    }

    @Provides
    public DatabaseSession getDatabaseSession(DatabaseHelper helper) {
        return helper.getSession();
    }

    @Provides
    public User getUser(RapidFtrApplication application) throws JSONException {
        return application.isLoggedIn() ? application.getCurrentUser() : null;
    }

    @Provides
    public SynchronisationAsyncTask<Child> getChildSynchronisationAsyncTask(User user, Provider<SyncAllDataAsyncTask<Child>> provider1, Provider<SyncUnverifiedDataAsyncTask<Child>> provider2) {