// BEGIN GENERATED BY /Users/kueda/projects/eclipse/workspace/iNaturalist/rails2android.rb AT Mon Jan 09 13:07:06 -0500 2012
package org.inaturalist.android;
import java.io.Serializable;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.tinylog.Logger;

import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;

@SuppressWarnings("WeakerAccess")
public class Observation implements BaseColumns, Serializable {
    //private static SimpleDateFormat DATETIME_ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
    private static SimpleDateFormat DATETIME_ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.ENGLISH);

    public Integer _id;
    public Timestamp _created_at;
    public Timestamp _synced_at;
    public Timestamp _updated_at;
    public Timestamp created_at;
    public String description;
    public String geoprivacy;
    public String taxon_geoprivacy;
    public Integer iconic_taxon_id;
    public String iconic_taxon_name;
    public Integer id;
    public Boolean id_please;
    public Double latitude;
    public Double longitude;
    public Timestamp observed_on;
    public String observed_on_string;
    public String time_zone;
    public String license;
    public Boolean out_of_range;
    public Boolean captive;
    public String place_guess;
    public String private_place_guess;
    public Integer positional_accuracy;
    public String positioning_device;
    public String positioning_method;
    public Double private_latitude;
    public Double private_longitude;
    public Integer private_positional_accuracy;
    public String quality_grade;
    public String species_guess;
    public Integer taxon_id;
    public Timestamp time_observed_at;
    public Timestamp updated_at;
    public String user_agent;
    public Integer user_id;
    public String user_login;
    public Integer comments_count;
    public Integer identifications_count;
    public Integer last_comments_count;
    public Integer last_identifications_count;
    public Boolean is_deleted;
    public String preferred_common_name;
    public Boolean prefers_community_taxon;
    public Boolean prefers_community_taxon_was;

    public SerializableJSONArray comments;
    public SerializableJSONArray identifications;
    public SerializableJSONArray favorites;

    public SerializableJSONArray field_values;

    public List<ObservationPhoto> photos;
    public List<ObservationSound> sounds;

    public Timestamp _created_at_was;
    public Timestamp _synced_at_was;
    public Timestamp _updated_at_was;
    public Timestamp created_at_was;
    public String description_was;
    public String geoprivacy_was;
    public String time_zone_was;
    public String license_was;
    public Integer iconic_taxon_id_was;
    public String iconic_taxon_name_was;
    public Integer id_was;
    public Boolean id_please_was;
    public Double latitude_was;
    public Double longitude_was;
    public Timestamp observed_on_was;
    public String observed_on_string_was;
    public Boolean out_of_range_was;
    public Boolean captive_was;
    public String place_guess_was;
    public String private_place_guess_was;
    public Integer positional_accuracy_was;
    public String positioning_device_was;
    public String positioning_method_was;
    public Double private_latitude_was;
    public Double private_longitude_was;
    public Integer private_positional_accuracy_was;
    public String quality_grade_was;
    public String species_guess_was;
    public Integer taxon_id_was;
    public Timestamp time_observed_at_was;
    public Timestamp updated_at_was;
    public String user_agent_was;
    public Integer user_id_was;
    public String user_login_was;
    public Boolean is_deleted_was;
    public SerializableJSONArray projects;
    public String uuid;
    public Boolean owners_identification_from_vision;
    public String scientific_name;
    public Integer rank_level;
    public String rank;


    public static final String TAG = "Observation";
    public static final String TABLE_NAME = "observations";
    public static final int OBSERVATIONS_URI_CODE = 1279;
    public static final int OBSERVATION_ID_URI_CODE = 1164;
    public static HashMap<String, String> PROJECTION_MAP;
    public static final String AUTHORITY = "org.inaturalist.android.observation";
    public static final Uri    CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/observations");
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.observation";
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.observation";
    public static final String DEFAULT_SORT_ORDER = "CASE WHEN _created_at > created_at THEN created_at ELSE _created_at END DESC, CASE WHEN id IS NULL THEN _id ELSE id END DESC";
    public static final String DEFAULT_SYNC_ORDER = "CASE WHEN _created_at IS NULL THEN created_at ELSE _created_at END ASC";
    public static final String SYNC_ORDER = "_created_at ASC";
    public static final String _CREATED_AT = "_created_at";
    public static final String _SYNCED_AT = "_synced_at";
    public static final String _UPDATED_AT = "_updated_at";
    public static final String CREATED_AT = "created_at";
    public static final String DESCRIPTION = "description";
    public static final String GEOPRIVACY = "geoprivacy";
    public static final String TAXON_GEOPRIVACY = "taxon_geoprivacy";
    public static final String ICONIC_TAXON_ID = "iconic_taxon_id";
    public static final String ICONIC_TAXON_NAME = "iconic_taxon_name";
    public static final String ID = "id";
    public static final String ID_PLEASE = "id_please";
    public static final String LATITUDE = "latitude";
    public static final String LONGITUDE = "longitude";
    public static final String OBSERVED_ON = "observed_on";
    public static final String OBSERVED_ON_STRING = "observed_on_string";
    public static final String TIME_ZONE = "time_zone";
    public static final String LICENSE = "license";
    public static final String OUT_OF_RANGE = "out_of_range";
    public static final String CAPTIVE = "captive";
    public static final String PLACE_GUESS = "place_guess";
    public static final String PRIVATE_PLACE_GUESS = "private_place_guess";
    public static final String POSITIONAL_ACCURACY = "positional_accuracy";
    public static final String POSITIONING_DEVICE = "positioning_device";
    public static final String POSITIONING_METHOD = "positioning_method";
    public static final String PRIVATE_LATITUDE = "private_latitude";
    public static final String PRIVATE_LONGITUDE = "private_longitude";
    public static final String PRIVATE_POSITIONAL_ACCURACY = "private_positional_accuracy";
    public static final String QUALITY_GRADE = "quality_grade";
    public static final String SPECIES_GUESS = "species_guess";
    public static final String PREFERRED_COMMON_NAME = "preferred_common_name";
    public static final String TAXON_ID = "taxon_id";
    public static final String TIME_OBSERVED_AT = "time_observed_at";
    public static final String UPDATED_AT = "updated_at";
    public static final String USER_AGENT = "user_agent";
    public static final String USER_ID = "user_id";
    public static final String USER_LOGIN = "user_login";
    public static final String COMMENTS_COUNT = "comments_count";
    public static final String IDENTIFICATIONS_COUNT = "identifications_count";
    public static final String LAST_COMMENTS_COUNT = "last_comments_count";
    public static final String LAST_IDENTIFICATIONS_COUNT = "last_identifications_count";
    public static final String IS_DELETED = "is_deleted";
    public static final String UUID = "uuid";
    public static final String OWNERS_IDENTIFICATION_FROM_VISION = "owners_identification_from_vision";
    public static final String SCIENTIFIC_NAME = "scientific_name";
    public static final String RANK_LEVEL = "rank_level";
    public static final String RANK = "rank";
    public static final String PREFERS_COMMUNITY_TAXON = "prefers_community_taxon";

    public static final String[] PROJECTION = new String[] {
        Observation._ID,
        Observation._CREATED_AT,
        Observation._SYNCED_AT,
        Observation._UPDATED_AT,
        Observation.CREATED_AT,
        Observation.DESCRIPTION,
        Observation.GEOPRIVACY,
        Observation.TAXON_GEOPRIVACY,
        Observation.ICONIC_TAXON_ID,
        Observation.ICONIC_TAXON_NAME,
        Observation.ID,
        Observation.ID_PLEASE,
        Observation.LATITUDE,
        Observation.LONGITUDE,
        Observation.OBSERVED_ON,
        Observation.OBSERVED_ON_STRING,
        Observation.TIME_ZONE,
        Observation.LICENSE,
        Observation.OUT_OF_RANGE,
        Observation.CAPTIVE,
        Observation.PLACE_GUESS,
        Observation.PRIVATE_PLACE_GUESS,
        Observation.POSITIONAL_ACCURACY,
        Observation.POSITIONING_DEVICE,
        Observation.POSITIONING_METHOD,
        Observation.PRIVATE_LATITUDE,
        Observation.PRIVATE_LONGITUDE,
        Observation.PRIVATE_POSITIONAL_ACCURACY,
        Observation.QUALITY_GRADE,
        Observation.SPECIES_GUESS,
        Observation.PREFERRED_COMMON_NAME,
        Observation.TAXON_ID,
        Observation.TIME_OBSERVED_AT,
        Observation.UPDATED_AT,
        Observation.USER_AGENT,
        Observation.USER_ID,
        Observation.USER_LOGIN,
        Observation.IDENTIFICATIONS_COUNT,
        Observation.COMMENTS_COUNT,
        Observation.LAST_COMMENTS_COUNT,
        Observation.LAST_IDENTIFICATIONS_COUNT,
        Observation.IS_DELETED,
        Observation.UUID,
        Observation.OWNERS_IDENTIFICATION_FROM_VISION,
        Observation.SCIENTIFIC_NAME,
        Observation.RANK_LEVEL,
        Observation.RANK,
        Observation.PREFERS_COMMUNITY_TAXON,
    };

    static {
        PROJECTION_MAP = new HashMap<String, String>();
        PROJECTION_MAP.put(Observation._ID, Observation._ID);
        PROJECTION_MAP.put(Observation._CREATED_AT, Observation._CREATED_AT);
        PROJECTION_MAP.put(Observation._SYNCED_AT, Observation._SYNCED_AT);
        PROJECTION_MAP.put(Observation._UPDATED_AT, Observation._UPDATED_AT);
        PROJECTION_MAP.put(Observation.CREATED_AT, Observation.CREATED_AT);
        PROJECTION_MAP.put(Observation.DESCRIPTION, Observation.DESCRIPTION);
        PROJECTION_MAP.put(Observation.GEOPRIVACY, Observation.GEOPRIVACY);
        PROJECTION_MAP.put(Observation.TAXON_GEOPRIVACY, Observation.TAXON_GEOPRIVACY);
        PROJECTION_MAP.put(Observation.ICONIC_TAXON_ID, Observation.ICONIC_TAXON_ID);
        PROJECTION_MAP.put(Observation.ICONIC_TAXON_NAME, Observation.ICONIC_TAXON_NAME);
        PROJECTION_MAP.put(Observation.ID, Observation.ID);
        PROJECTION_MAP.put(Observation.ID_PLEASE, Observation.ID_PLEASE);
        PROJECTION_MAP.put(Observation.LATITUDE, Observation.LATITUDE);
        PROJECTION_MAP.put(Observation.LONGITUDE, Observation.LONGITUDE);
        PROJECTION_MAP.put(Observation.OBSERVED_ON, Observation.OBSERVED_ON);
        PROJECTION_MAP.put(Observation.OBSERVED_ON_STRING, Observation.OBSERVED_ON_STRING);
        PROJECTION_MAP.put(Observation.TIME_ZONE, Observation.TIME_ZONE);
        PROJECTION_MAP.put(Observation.LICENSE, Observation.LICENSE);
        PROJECTION_MAP.put(Observation.OUT_OF_RANGE, Observation.OUT_OF_RANGE);
        PROJECTION_MAP.put(Observation.CAPTIVE, Observation.CAPTIVE);
        PROJECTION_MAP.put(Observation.PLACE_GUESS, Observation.PLACE_GUESS);
        PROJECTION_MAP.put(Observation.PRIVATE_PLACE_GUESS, Observation.PRIVATE_PLACE_GUESS);
        PROJECTION_MAP.put(Observation.UUID, Observation.UUID);
        PROJECTION_MAP.put(Observation.OWNERS_IDENTIFICATION_FROM_VISION, Observation.OWNERS_IDENTIFICATION_FROM_VISION);
        PROJECTION_MAP.put(Observation.SCIENTIFIC_NAME, Observation.SCIENTIFIC_NAME);
        PROJECTION_MAP.put(Observation.RANK_LEVEL, Observation.RANK_LEVEL);
        PROJECTION_MAP.put(Observation.RANK, Observation.RANK);
        PROJECTION_MAP.put(Observation.POSITIONAL_ACCURACY, Observation.POSITIONAL_ACCURACY);
        PROJECTION_MAP.put(Observation.POSITIONING_DEVICE, Observation.POSITIONING_DEVICE);
        PROJECTION_MAP.put(Observation.POSITIONING_METHOD, Observation.POSITIONING_METHOD);
        PROJECTION_MAP.put(Observation.PRIVATE_LATITUDE, Observation.PRIVATE_LATITUDE);
        PROJECTION_MAP.put(Observation.PRIVATE_LONGITUDE, Observation.PRIVATE_LONGITUDE);
        PROJECTION_MAP.put(Observation.PRIVATE_POSITIONAL_ACCURACY, Observation.PRIVATE_POSITIONAL_ACCURACY);
        PROJECTION_MAP.put(Observation.QUALITY_GRADE, Observation.QUALITY_GRADE);
        PROJECTION_MAP.put(Observation.SPECIES_GUESS, Observation.SPECIES_GUESS);
        PROJECTION_MAP.put(Observation.PREFERRED_COMMON_NAME, Observation.PREFERRED_COMMON_NAME);
        PROJECTION_MAP.put(Observation.TAXON_ID, Observation.TAXON_ID);
        PROJECTION_MAP.put(Observation.TIME_OBSERVED_AT, Observation.TIME_OBSERVED_AT);
        PROJECTION_MAP.put(Observation.UPDATED_AT, Observation.UPDATED_AT);
        PROJECTION_MAP.put(Observation.USER_AGENT, Observation.USER_AGENT);
        PROJECTION_MAP.put(Observation.USER_ID, Observation.USER_ID);
        PROJECTION_MAP.put(Observation.USER_LOGIN, Observation.USER_LOGIN);
        PROJECTION_MAP.put(Observation.IDENTIFICATIONS_COUNT, Observation.IDENTIFICATIONS_COUNT);
        PROJECTION_MAP.put(Observation.COMMENTS_COUNT, Observation.COMMENTS_COUNT);
        PROJECTION_MAP.put(Observation.LAST_IDENTIFICATIONS_COUNT, Observation.LAST_IDENTIFICATIONS_COUNT);
        PROJECTION_MAP.put(Observation.LAST_COMMENTS_COUNT, Observation.LAST_COMMENTS_COUNT);
        PROJECTION_MAP.put(Observation.IS_DELETED, Observation.IS_DELETED);
        PROJECTION_MAP.put(Observation.PREFERS_COMMUNITY_TAXON, Observation.PREFERS_COMMUNITY_TAXON);
    }

    public Observation() {}

    public Observation(Cursor c) {
        if (c.getPosition() == -1) c.moveToFirst();
        BetterCursor bc = new BetterCursor(c);
        this._id = bc.getInt(_ID);
        this._created_at = bc.getTimestamp(_CREATED_AT);
        this._created_at_was = this._created_at;
        this._synced_at = bc.getTimestamp(_SYNCED_AT);
        this._synced_at_was = this._synced_at;
        this._updated_at = bc.getTimestamp(_UPDATED_AT);
        this._updated_at_was = this._updated_at;
        this.created_at = bc.getTimestamp(CREATED_AT);
        this.created_at_was = this.created_at;
        this.description = bc.getString(DESCRIPTION);
        this.description_was = this.description;
        this.geoprivacy = bc.getString(GEOPRIVACY);
        this.geoprivacy_was = this.geoprivacy;
        this.taxon_geoprivacy = bc.getString(TAXON_GEOPRIVACY);
        this.iconic_taxon_id = bc.getInteger(ICONIC_TAXON_ID);
        this.iconic_taxon_id_was = this.iconic_taxon_id;
        this.iconic_taxon_name = bc.getString(ICONIC_TAXON_NAME);
        this.iconic_taxon_name_was = this.iconic_taxon_name;
        this.id = bc.getInteger(ID);
        this.id_was = this.id;
        this.id_please = bc.getBoolean(ID_PLEASE);
        this.id_please_was = this.id_please;
        this.latitude = bc.getDouble(LATITUDE);
        this.latitude_was = this.latitude;
        this.longitude = bc.getDouble(LONGITUDE);
        this.longitude_was = this.longitude;
        this.observed_on = bc.getTimestamp(OBSERVED_ON);
        this.observed_on_was = this.observed_on;
        this.time_zone = bc.getString(TIME_ZONE);
        this.time_zone_was = this.time_zone;
        this.license = bc.getString(LICENSE);
        this.license_was = this.license;
        this.observed_on_string = bc.getString(OBSERVED_ON_STRING);
        this.observed_on_string_was = this.observed_on_string;
        this.out_of_range = bc.getBoolean(OUT_OF_RANGE);
        this.out_of_range_was = this.out_of_range;
        this.captive = bc.getBoolean(CAPTIVE);
        this.captive_was = this.captive;
        this.place_guess = bc.getString(PLACE_GUESS);
        this.place_guess_was = this.place_guess;
        this.private_place_guess = bc.getString(PRIVATE_PLACE_GUESS);
        this.private_place_guess_was = this.private_place_guess;
        this.uuid = bc.getString(UUID);
        this.owners_identification_from_vision = bc.getBoolean(OWNERS_IDENTIFICATION_FROM_VISION);
        this.scientific_name = bc.getString(SCIENTIFIC_NAME);
        this.rank_level = bc.getInteger(RANK_LEVEL);
        this.rank = bc.getString(RANK);
        this.positional_accuracy = bc.getInteger(POSITIONAL_ACCURACY);
        this.positional_accuracy_was = this.positional_accuracy;
        this.positioning_device = bc.getString(POSITIONING_DEVICE);
        this.positioning_device_was = this.positioning_device;
        this.positioning_method = bc.getString(POSITIONING_METHOD);
        this.positioning_method_was = this.positioning_method;
        this.private_latitude = bc.getDouble(PRIVATE_LATITUDE);
        this.private_latitude_was = this.private_latitude;
        this.private_longitude = bc.getDouble(PRIVATE_LONGITUDE);
        this.private_longitude_was = this.private_longitude;
        this.private_positional_accuracy = bc.getInteger(PRIVATE_POSITIONAL_ACCURACY);
        this.private_positional_accuracy_was = this.private_positional_accuracy;
        this.quality_grade = bc.getString(QUALITY_GRADE);
        this.quality_grade_was = this.quality_grade;
        this.species_guess = bc.getString(SPECIES_GUESS);
        this.species_guess_was = this.species_guess;
        this.preferred_common_name = bc.getString(PREFERRED_COMMON_NAME);
        this.taxon_id = bc.getInteger(TAXON_ID);
        this.taxon_id_was = this.taxon_id;
        this.time_observed_at = bc.getTimestamp(TIME_OBSERVED_AT);
        this.time_observed_at_was = this.time_observed_at;
        this.updated_at = bc.getTimestamp(UPDATED_AT);
        this.updated_at_was = this.updated_at;
        this.user_agent = bc.getString(USER_AGENT);
        this.user_agent_was = this.user_agent;
        this.user_id = bc.getInteger(USER_ID);
        this.user_id_was = this.user_id;
        this.user_login = bc.getString(USER_LOGIN);
        this.user_login_was = this.user_login;
        this.is_deleted = bc.getBoolean(IS_DELETED);
        this.is_deleted_was = this.is_deleted;
        Object value = bc.get(PREFERS_COMMUNITY_TAXON);
        this.prefers_community_taxon = value == null ? null : Boolean.valueOf((String)value);
        this.prefers_community_taxon_was = this.prefers_community_taxon;

        this.comments_count = bc.getInteger(COMMENTS_COUNT);
        this.identifications_count = bc.getInteger(IDENTIFICATIONS_COUNT);
        this.last_comments_count = bc.getInteger(LAST_COMMENTS_COUNT);
        this.last_identifications_count = bc.getInteger(LAST_IDENTIFICATIONS_COUNT);

    }

    public Observation(BetterJSONObject o) {
        this._created_at = o.getTimestamp("_created_at");
        this._created_at_was = this._created_at;
        this._synced_at = o.getTimestamp("_synced_at");
        this._synced_at_was = this._synced_at;
        this._updated_at = o.getTimestamp("_updated_at");
        this._updated_at_was = this._updated_at;
        this.created_at = o.getTimestamp("created_at");
        this.created_at_was = this.created_at;
        this.description = o.getString("description");
        this.description_was = this.description;
        this.geoprivacy = o.getString("geoprivacy");
        this.geoprivacy_was = this.geoprivacy;
        this.taxon_geoprivacy = o.getString("taxon_geoprivacy");
        this.iconic_taxon_id = o.getInteger("iconic_taxon_id");
        this.iconic_taxon_id_was = this.iconic_taxon_id;
        this.iconic_taxon_name = o.getString("iconic_taxon_name");
        this.iconic_taxon_name_was = this.iconic_taxon_name;
        this.id = o.getInteger("id");
        this.id_was = this.id;
        this.id_please = o.getBoolean("id_please");
        this.id_please_was = this.id_please;
        this.latitude = o.getDouble("latitude");
        this.latitude_was = this.latitude;
        this.longitude = o.getDouble("longitude");
        this.longitude_was = this.longitude;
        this.observed_on = o.getTimestamp(o.has("time_observed_at") && !o.isNull("time_observed_at") ?
                "time_observed_at" : "observed_on");
        this.observed_on_was = this.observed_on;
        this.observed_on_string = o.getString("observed_on_string");
        this.observed_on_string_was = this.observed_on_string;
        this.time_zone = o.getString("time_zone");
        this.time_zone_was = this.time_zone;
        this.license = o.getString("license_code");
        if (this.license != null) this.license = this.license.toLowerCase();
        this.license_was = this.license;
        this.out_of_range = o.getBoolean("out_of_range");
        this.out_of_range_was = this.out_of_range;
        this.captive = o.getBoolean("captive");
        this.captive_was = this.captive;
        this.place_guess = o.getString("place_guess");
        this.place_guess_was = this.place_guess;
        this.private_place_guess = o.getString("private_place_guess");
        this.private_place_guess_was = this.private_place_guess;
        this.uuid = o.getString("uuid");
        this.owners_identification_from_vision = o.getBoolean("owners_identification_from_vision");
        this.positional_accuracy = o.getInteger("positional_accuracy");
        this.positional_accuracy_was = this.positional_accuracy;
        this.positioning_device = o.getString("positioning_device");
        this.positioning_device_was = this.positioning_device;
        this.positioning_method = o.getString("positioning_method");
        this.positioning_method_was = this.positioning_method;
        this.private_latitude = o.getDouble("private_latitude");
        this.private_latitude_was = this.private_latitude;
        this.private_longitude = o.getDouble("private_longitude");
        this.private_longitude_was = this.private_longitude;
        this.private_positional_accuracy = o.getInteger("private_positional_accuracy");
        this.private_positional_accuracy_was = this.private_positional_accuracy;
        this.quality_grade = o.getString("quality_grade");
        this.quality_grade_was = this.quality_grade;
        this.species_guess = o.getString("species_guess");
        this.species_guess_was = this.species_guess;
        this.taxon_id = o.getInteger("taxon_id");
        this.taxon_id_was = this.taxon_id;
        this.time_observed_at = o.getTimestamp("time_observed_at");
        this.time_observed_at_was = this.time_observed_at;
        this.updated_at = o.getTimestamp("updated_at");
        this.updated_at_was = this.updated_at;
        this.user_agent = o.getString("user_agent");
        this.user_agent_was = this.user_agent;
        this.user_id = o.getInteger("user_id");
        this.user_id_was = this.user_id;
        this.user_login = o.getString("user_login");
        this.user_login_was = this.user_login;
        this.is_deleted_was = this.is_deleted;
        JSONObject preferences = o.getJSONObject("preferences");
        this.prefers_community_taxon = (preferences == null) || (preferences.isNull("prefers_community_taxon")) ? null : preferences.optBoolean("prefers_community_taxon");
        this.prefers_community_taxon_was = this.prefers_community_taxon;

        this.comments = o.getJSONArray("comments");
        this.identifications = o.getJSONArray("identifications");
        this.favorites = o.getJSONArray("faves");

        try {
            this.photos = new ArrayList<ObservationPhoto>();
            JSONArray photos;
            photos = o.getJSONObject().getJSONArray("observation_photos");
            for (int i = 0; i < photos.length(); i++) {
                BetterJSONObject json = new BetterJSONObject((JSONObject)photos.get(i));
                ObservationPhoto photo = new ObservationPhoto(json);
                photo.observation_id = o.getInt("id");
                photo._observation_id = this._id;
                this.photos.add(photo);
            }
        } catch (JSONException e) {
            if (!e.getMessage().matches("No value for observation_photos")) {
                Logger.tag(TAG).error(e);
            }
        }

        try {
            this.sounds = new ArrayList<ObservationSound>();
            JSONArray sounds;
            sounds = o.getJSONObject().getJSONArray("observation_sounds");
            for (int i = 0; i < sounds.length(); i++) {
                BetterJSONObject json = new BetterJSONObject((JSONObject)sounds.get(i));
                ObservationSound sound = new ObservationSound(json);
                sound.observation_id = o.getInt("id");
                sound._observation_id = this._id;
                this.sounds.add(sound);
            }
        } catch (JSONException e) {
            if (!e.getMessage().matches("No value for observation_sounds")) {
                Logger.tag(TAG).error(e);
            }
        }


        this.comments_count = o.getInteger("comments_count");
        if ((this.comments != null) && (this.comments.getJSONArray().length() != this.comments_count)) {
            // Actually count the number of comments just in case the count field hasn't been updated by the server yet
            this.comments_count = this.comments.getJSONArray().length();
        }
        this.last_comments_count = this.comments_count;

        this.identifications_count = o.getInteger("identifications_count");
        if ((this.identifications != null) && (this.identifications.getJSONArray().length() != this.identifications_count)) {
            // Actually count the number of identifications just in case the count field hasn't been updated by the server yet
            this.identifications_count = this.identifications.getJSONArray().length();
        }
        this.last_identifications_count = this.identifications_count;

        this.projects = o.getJSONArray("project_observations");
        if ((o.has("non_traditional_projects")) && (o.getJSONArray("non_traditional_projects").getJSONArray().length() > 0)) {
            // Also add umbrella projects
            JSONArray projects = this.projects.getJSONArray();
            JSONArray umbrellaProjects = o.getJSONObject().optJSONArray("non_traditional_projects");
            for (int i = 0; i < umbrellaProjects.length(); i++) {
                projects.put(umbrellaProjects.optJSONObject(i));
            }

            this.projects = new SerializableJSONArray(projects);
        }

        this.field_values = o.getJSONArray("observation_field_values");

        this.preferred_common_name = null;
        setPreferredCommonName(o.getJSONObject());

        JSONObject user = o.getJSONObject("user");
        if (user != null) {
            this.user_id = user.optInt("id");
            this.user_login = user.optString("login");
        }

        if (o.has("place")) {
            String location = o.getString("place");
            if ((location != null) && (location.length() > 0)) {
                String[] locationSplit = location.split(",");
                this.latitude = Double.parseDouble(locationSplit[0]);
                this.latitude_was = this.latitude;
                this.longitude = Double.parseDouble(locationSplit[1]);
                this.longitude_was = this.longitude;
            }
        }

        if (o.has("private_location")) {
            String location = o.getString("private_location");
            if ((location != null) && (location.length() > 0)) {
                String[] locationSplit = location.split(",");
                this.private_latitude = Double.parseDouble(locationSplit[0]);
                this.private_latitude_was = this.private_latitude;
                this.private_longitude = Double.parseDouble(locationSplit[1]);
                this.private_longitude_was = this.private_longitude;
            }
        }

        if (o.has("location")) {
            String location = o.getString("location");
            if ((location != null) && (location.length() > 0) && (!location.equals("null"))) {
                String[] locationSplit = location.split(",");
                if ((locationSplit.length == 2) && (!locationSplit[0].equals("null")) && (!locationSplit[1].equals("null"))) {
                    this.latitude = Double.parseDouble(locationSplit[0]);
                    this.latitude_was = this.latitude;
                    this.longitude = Double.parseDouble(locationSplit[1]);
                    this.longitude_was = this.longitude;
                }
            }
        }
    }

    public void setPreferredCommonName(JSONObject o) {
        JSONObject taxon = o.optJSONObject("taxon");
        if (taxon != null) {
            if (taxon.has("common_name") && !taxon.isNull("common_name")) {
                try {
                    this.preferred_common_name = taxon.getJSONObject("common_name").optString("name");
                } catch (JSONException e) {
                }
            } else {
                if (taxon.has("preferred_common_name") && !taxon.isNull("preferred_common_name")) {
                    this.preferred_common_name = taxon.optString("preferred_common_name");
                } else {
                    this.preferred_common_name = taxon.optString("name");
                }
            }

            if (taxon.has("id")) this.taxon_id = taxon.optInt("id");
            if (taxon.has("iconic_taxon_name")) this.iconic_taxon_name = taxon.optString("iconic_taxon_name");
            if (taxon.has("iconic_taxon_id")) this.iconic_taxon_id = taxon.optInt("iconic_taxon_id");

            this.rank_level = taxon.optInt("rank_level");
            this.scientific_name = taxon.optString("name", "");
            this.rank = taxon.optString("rank");
        }
    }

    @Override
    public String toString() {
        return "Observation(id: " + id + ", _id: " + _id + ", uuid: " + uuid + ", _synced_at: " + _synced_at + ", _updated_at: " + _updated_at + ", is_deleted: " + is_deleted + ", photos: " + photos + ")";
    }

    public JSONObject toJSONObject() {
        return toJSONObject(false);
    }

    public JSONObject toJSONObject(boolean partial) {
        BetterJSONObject bo = new BetterJSONObject();
        bo.put("_created_at", _created_at);
        bo.put("_synced_at", _synced_at);
        bo.put("_updated_at", _updated_at);
        bo.put("created_at", created_at);
        bo.put("description", description);
        bo.put("geoprivacy", geoprivacy);
        bo.put("taxon_geoprivacy", taxon_geoprivacy);
        bo.put("iconic_taxon_id", iconic_taxon_id);
        bo.put("iconic_taxon_name", iconic_taxon_name);
        bo.put("id", id);
        if (!partial) bo.put("id_please", id_please);
        bo.put("latitude", latitude == null ? null : latitude.toString().replace(',', '.'));
        bo.put("longitude", longitude == null ? null : longitude.toString().replace(',', '.'));
        if ((latitude == null) && (longitude == null)) bo.put("location", null);
        bo.put("observed_on", observed_on);
        bo.put("observed_on_string", observed_on_string);
        bo.put("time_zone", time_zone);
        bo.put("license_code", license);
        if (!partial) bo.put("out_of_range", out_of_range);
        bo.put("captive_flag", captive);
        bo.put("place_guess", (private_place_guess != null) && (private_place_guess.length() > 0) ? private_place_guess : place_guess);
        bo.put("private_place_guess", private_place_guess);
        bo.put("uuid", uuid);
        bo.put("owners_identification_from_vision", owners_identification_from_vision != null ? owners_identification_from_vision : false);
        if (!partial) bo.put("scientific_name", scientific_name);
        if (!partial) bo.put("rank_level", rank_level);
        if (!partial) bo.put("rank", rank);
        bo.put("positional_accuracy", positional_accuracy);
        bo.put("positioning_device", positioning_device);
        bo.put("positioning_method", positioning_method);
        bo.put("private_latitude", private_latitude == null ? null : private_latitude.toString().replace(',', '.'));
        bo.put("private_longitude", private_longitude == null ? null : private_longitude.toString().replace(',', '.'));
        if ((private_latitude == null) && (private_longitude == null)) bo.put("private_location", null);
        bo.put("private_positional_accuracy", private_positional_accuracy);
        bo.put("quality_grade", quality_grade);
        bo.put("species_guess", species_guess);
        bo.put("taxon_id", taxon_id);
        if (time_observed_at != null) bo.put("time_observed_at", DATETIME_ISO_FORMAT.format(time_observed_at));
        bo.put("updated_at", updated_at);
        bo.put("user_agent", user_agent);
        bo.put("user_id", user_id);
        bo.put("user_login", user_login);
        if (!partial) bo.put("identifications_count", identifications_count);
        if (!partial) bo.put("comment_count", comments_count);
        bo.put("prefers_community_taxon", prefers_community_taxon);

        return bo.getJSONObject();
    }

    public Uri getUri() {
        if (_id == null) {
            return null;
        } else {
            return ContentUris.withAppendedId(CONTENT_URI, _id);
        }
    }

    private boolean areFieldsEqual(Object o1, Object o2) {
        if ((o1 != null) && (o2 != null)) return o1.equals(o2);
        if ((o1 == null) && (o2 != null)) return false;
        if ((o1 != null) && (o2 == null)) return false;
        return true;
    }

    public boolean equals(Observation observation) {
        if (!areFieldsEqual(this.updated_at, observation.updated_at)) return false;
        if (!areFieldsEqual(this.created_at, observation.created_at)) return false;
        if (!areFieldsEqual(this.description, observation.description)) return false;
        if (!areFieldsEqual(this.geoprivacy, observation.geoprivacy)) return false;
        if (!areFieldsEqual(this.iconic_taxon_id, observation.iconic_taxon_id)) return false;
        if (!areFieldsEqual(this.iconic_taxon_name, observation.iconic_taxon_name)) return false;
        if (!areFieldsEqual(this.preferred_common_name, observation.preferred_common_name)) return false;
        if (!areFieldsEqual(this.id, observation.id)) return false;
        if (!areFieldsEqual(this.id_please, observation.id_please)) return false;
        if (!areFieldsEqual(this.latitude, observation.latitude)) return false;
        if (!areFieldsEqual(this.longitude, observation.longitude)) return false;
        if (!areFieldsEqual(this.observed_on, observation.observed_on)) return false;
        if (!areFieldsEqual(this.observed_on_string, observation.observed_on_string)) return false;
        if (!areFieldsEqual(this.time_zone, observation.time_zone)) return false;
        if (!areFieldsEqual(this.license, observation.license)) return false;
        if (!areFieldsEqual(this.out_of_range, observation.out_of_range)) return false;
        if (!areFieldsEqual(this.captive, observation.captive)) return false;
        if (!areFieldsEqual(this.place_guess, observation.place_guess)) return false;
        if (!areFieldsEqual(this.private_place_guess, observation.private_place_guess)) return false;
        if (!areFieldsEqual(this.uuid, observation.uuid)) return false;
        if (!areFieldsEqual(this.owners_identification_from_vision, observation.owners_identification_from_vision)) return false;
        if (!areFieldsEqual(this.rank_level, observation.rank_level)) return false;
        if (!areFieldsEqual(this.rank, observation.rank)) return false;
        if (!areFieldsEqual(this.scientific_name, observation.scientific_name)) return false;
        if (!areFieldsEqual(this.positional_accuracy, observation.positional_accuracy)) return false;
        if (!areFieldsEqual(this.positioning_device, observation.positioning_device)) return false;
        if (!areFieldsEqual(this.positioning_method, observation.positioning_method)) return false;
        if (!areFieldsEqual(this.private_latitude, observation.private_latitude)) return false;
        if (!areFieldsEqual(this.private_longitude, observation.private_longitude)) return false;
        if (!areFieldsEqual(this.private_positional_accuracy, observation.private_positional_accuracy)) return false;
        if (!areFieldsEqual(this.quality_grade, observation.quality_grade)) return false;
        if (!areFieldsEqual(this.species_guess, observation.species_guess)) return false;
        if (!areFieldsEqual(this.taxon_id, observation.taxon_id)) return false;
        if (!areFieldsEqual(this.time_observed_at, observation.time_observed_at)) return false;
        if (!areFieldsEqual(this.updated_at, observation.updated_at)) return false;
        if (!areFieldsEqual(this.user_agent, observation.user_agent)) return false;
        if (!areFieldsEqual(this.user_id, observation.user_id)) return false;
        if (!areFieldsEqual(this.user_login, observation.user_login)) return false;
        if (!areFieldsEqual(this.comments_count, observation.comments_count)) return false;
        if (!areFieldsEqual(this.identifications_count, observation.identifications_count)) return false;

        return true;
    }

    // Returns true if observation was modified because of the merge
    public boolean merge(Observation observation) {
        if ((observation.updated_at != null) && this._updated_at.before(observation.updated_at)) {
            // overwrite
            this.created_at = observation.created_at;
            this.description = observation.description;
            this.geoprivacy = observation.geoprivacy;
            this.taxon_geoprivacy = observation.taxon_geoprivacy;
            this.iconic_taxon_id = observation.iconic_taxon_id;
            this.iconic_taxon_name = observation.iconic_taxon_name;
            if (observation.preferred_common_name != null) this.preferred_common_name = observation.preferred_common_name;
            this.id = observation.id;
            this.id_please = observation.id_please;
            this.latitude = observation.latitude;
            this.longitude = observation.longitude;
            this.observed_on = observation.observed_on;
            this.observed_on_string = observation.observed_on_string;
            this.time_zone = observation.time_zone;
            this.license = observation.license;
            this.out_of_range = observation.out_of_range;
            this.captive = observation.captive;
            this.place_guess = observation.place_guess;
            this.private_place_guess = observation.private_place_guess;
            this.uuid = observation.uuid;
            this.owners_identification_from_vision = observation.owners_identification_from_vision;
            this.rank_level = observation.rank_level;
            this.rank = observation.rank;
            this.scientific_name = observation.scientific_name;
            this.positional_accuracy = observation.positional_accuracy;
            this.positioning_device = observation.positioning_device;
            this.positioning_method = observation.positioning_method;
            this.private_latitude = observation.private_latitude;
            this.private_longitude = observation.private_longitude;
            this.private_positional_accuracy = observation.private_positional_accuracy;
            this.quality_grade = observation.quality_grade;
            this.species_guess = observation.species_guess;
            this.taxon_id = observation.taxon_id;
            this.time_observed_at = observation.time_observed_at;
            this.updated_at = observation.updated_at;
            this.user_agent = observation.user_agent;
            this.user_id = observation.user_id;
            this.user_login = observation.user_login;
            this.comments_count = observation.comments_count;
            this.identifications_count = observation.identifications_count;

            return true;
        } else {
            // set if null
            boolean isModified = false;
            
            if ((this.created_at == null) && (observation.created_at != null)) { this.created_at = observation.created_at; isModified = true; }
            if ((this.description == null) && (observation.description != null)) { this.description = observation.description; isModified = true; }
            if ((this.geoprivacy == null) && (observation.geoprivacy != null)) { this.geoprivacy = observation.geoprivacy; isModified = true; }
            if ((this.taxon_geoprivacy == null) && (observation.taxon_geoprivacy != null)) { this.taxon_geoprivacy = observation.taxon_geoprivacy; isModified = true; }
            if ((this.iconic_taxon_id == null) && (observation.iconic_taxon_id != null)) { this.iconic_taxon_id = observation.iconic_taxon_id; isModified = true; }
            if ((this.iconic_taxon_name == null) && (observation.iconic_taxon_name != null)) { this.iconic_taxon_name = observation.iconic_taxon_name; isModified = true; }
            if ((this.id == null) && (observation.id != null)) { this.id = observation.id; isModified = true; }
            if ((this.id_please == null) && (observation.id_please != null)) { this.id_please = observation.id_please; isModified = true; }
            if ((this.latitude == null) && (observation.latitude != null)) { this.latitude = observation.latitude; isModified = true; }
            if ((this.longitude == null) && (observation.longitude != null)) { this.longitude = observation.longitude; isModified = true; }
            if ((this.observed_on == null) && (observation.observed_on != null)) { this.observed_on = observation.observed_on; isModified = true; }
            if ((this.observed_on_string == null) && (observation.observed_on_string != null)) { this.observed_on_string = observation.observed_on_string; isModified = true; }
            if ((this.time_zone == null) && (observation.time_zone != null)) { this.time_zone = observation.time_zone; isModified = true; }
            if ((this.license == null) && (observation.license != null)) { this.license = observation.license; isModified = true; }
            if ((this.out_of_range == null) && (observation.out_of_range != null)) { this.out_of_range = observation.out_of_range; isModified = true; }
            if ((this.captive == null) && (observation.captive != null)) { this.captive = observation.captive; isModified = true; }
            if ((this.place_guess == null) && (observation.place_guess != null)) { this.place_guess = observation.place_guess; isModified = true; }
            if ((this.private_place_guess == null) && (observation.private_place_guess != null)) { this.private_place_guess = observation.private_place_guess; isModified = true; }
            if ((this.uuid == null) && (observation.uuid != null)) { this.uuid = observation.uuid; isModified = true; }
            if ((this.owners_identification_from_vision == null) && (observation.owners_identification_from_vision != null)) { this.owners_identification_from_vision = observation.owners_identification_from_vision; isModified = true; }
            if ((this.scientific_name == null) && (observation.scientific_name != null)) { this.scientific_name = observation.scientific_name; isModified = true; }
            if ((this.rank_level == null) && (observation.rank_level != null)) { this.rank_level = observation.rank_level; isModified = true; }
            if ((this.rank == null) && (observation.rank != null)) { this.rank = observation.rank; isModified = true; }
            if ((this.positional_accuracy == null) && (observation.positional_accuracy != null)) { this.positional_accuracy = observation.positional_accuracy; isModified = true; }
            if ((this.positioning_device == null) && (observation.positioning_device != null)) { this.positioning_device = observation.positioning_device; isModified = true; }
            if ((this.positioning_method == null) && (observation.positioning_method != null)) { this.positioning_method = observation.positioning_method; isModified = true; }
            if ((this.private_latitude == null) && (observation.private_latitude != null)) { this.private_latitude = observation.private_latitude; isModified = true; }
            if ((this.private_longitude == null) && (observation.private_longitude != null)) { this.private_longitude = observation.private_longitude; isModified = true; }
            if ((this.private_positional_accuracy == null) && (observation.private_positional_accuracy != null)) { this.private_positional_accuracy = observation.private_positional_accuracy; isModified = true; }
            if ((this.quality_grade == null) && (observation.quality_grade != null)) { this.quality_grade = observation.quality_grade; isModified = true; }
            if ((this.species_guess == null) && (observation.species_guess != null)) { this.species_guess = observation.species_guess; isModified = true; }
            if ((this.preferred_common_name == null) && (observation.preferred_common_name != null)) { this.preferred_common_name = observation.preferred_common_name; isModified = true; }
            if ((this.taxon_id == null) && (observation.taxon_id != null)) { this.taxon_id = observation.taxon_id; isModified = true; }
            if ((this.time_observed_at == null) && (observation.time_observed_at != null)) { this.time_observed_at = observation.time_observed_at; isModified = true; }
            if ((this.updated_at == null) && (observation.updated_at != null)) { this.updated_at = observation.updated_at; isModified = true; }
            if ((this.user_agent == null) && (observation.user_agent != null)) { this.user_agent = observation.user_agent; isModified = true; }
            if ((this.user_id == null) && (observation.user_id != null)) { this.user_id = observation.user_id; isModified = true; }
            if ((this.user_login == null) && (observation.user_login != null)) { this.user_login = observation.user_login; isModified = true; }
            if ((this.comments_count == null) && (observation.comments_count != null)) { this.comments_count = observation.comments_count; isModified = true; }
            if ((this.identifications_count == null) && (observation.identifications_count != null)) { this.identifications_count = observation.identifications_count; isModified = true; }

            return isModified;
        }
    }
    public ContentValues getContentValues() {
        return getContentValues(false);
    }

    public ContentValues getContentValues(boolean forceNullValues) {
        ContentValues cv = new ContentValues();
        if (created_at != null) cv.put(CREATED_AT, created_at.getTime());
        cv.put(DESCRIPTION, description);
        cv.put(GEOPRIVACY, geoprivacy);
        cv.put(TAXON_GEOPRIVACY, taxon_geoprivacy);
        cv.put(ICONIC_TAXON_ID, iconic_taxon_id);
        cv.put(ICONIC_TAXON_NAME, iconic_taxon_name);
        cv.put(ID, id);
        cv.put(ID_PLEASE, id_please);
        cv.put(LATITUDE, latitude);
        cv.put(LONGITUDE, longitude);
        if (observed_on != null || forceNullValues) {
            if (observed_on == null) {
                cv.putNull(OBSERVED_ON);
            } else {
                cv.put(OBSERVED_ON, observed_on.getTime());
            }
        }
        cv.put(OBSERVED_ON_STRING, observed_on_string);
        cv.put(TIME_ZONE, time_zone);
        cv.put(LICENSE, license);
        cv.put(OUT_OF_RANGE, out_of_range);
        cv.put(CAPTIVE, captive);
        cv.put(PLACE_GUESS, place_guess);
        cv.put(PRIVATE_PLACE_GUESS, private_place_guess);
        cv.put(UUID, uuid);
        cv.put(OWNERS_IDENTIFICATION_FROM_VISION, owners_identification_from_vision != null ? owners_identification_from_vision : false);
        cv.put(SCIENTIFIC_NAME, scientific_name);
        cv.put(RANK_LEVEL, rank_level != null ? rank_level : 99);
        cv.put(RANK, rank);
        cv.put(POSITIONAL_ACCURACY, positional_accuracy);
        cv.put(POSITIONING_DEVICE, positioning_device);
        cv.put(POSITIONING_METHOD, positioning_method);
        cv.put(PRIVATE_LATITUDE, private_latitude);
        cv.put(PRIVATE_LONGITUDE, private_longitude);
        cv.put(PRIVATE_POSITIONAL_ACCURACY, private_positional_accuracy);
        cv.put(QUALITY_GRADE, quality_grade);
        cv.put(SPECIES_GUESS, species_guess);
        cv.put(PREFERRED_COMMON_NAME, preferred_common_name);
        cv.put(TAXON_ID, taxon_id);
        if (time_observed_at != null || forceNullValues) {
            if (time_observed_at == null) {
                cv.putNull(TIME_OBSERVED_AT);
            } else {
                cv.put(TIME_OBSERVED_AT, time_observed_at.getTime());
            }
        }
        if (updated_at != null) cv.put(UPDATED_AT, updated_at.getTime());
        cv.put(USER_AGENT, user_agent);
        cv.put(USER_ID, user_id);
        cv.put(USER_LOGIN, user_login);
        cv.put(COMMENTS_COUNT, comments_count);
        cv.put(IDENTIFICATIONS_COUNT, identifications_count);
        cv.put(LAST_COMMENTS_COUNT, last_comments_count);
        cv.put(LAST_IDENTIFICATIONS_COUNT, last_identifications_count);
        cv.put(IS_DELETED, is_deleted);
        if (prefers_community_taxon == null) {
            cv.putNull(PREFERS_COMMUNITY_TAXON);
        } else {
            cv.put(PREFERS_COMMUNITY_TAXON, prefers_community_taxon.toString());
        }

        return cv;
    }

    public static String sqlCreate() {
        return "CREATE TABLE " + TABLE_NAME + " ("
                + Observation._ID + " INTEGER PRIMARY KEY,"
                + "_created_at INTEGER,"
                + "_synced_at INTEGER,"
                + "_updated_at INTEGER,"
                + "created_at INTEGER,"
                + "description TEXT,"
                + "geoprivacy TEXT,"
                + "taxon_geoprivacy TEXT,"
                + "iconic_taxon_id INTEGER,"
                + "iconic_taxon_name TEXT,"
                + "id INTEGER,"
                + "id_please INTEGER,"
                + "latitude REAL,"
                + "longitude REAL,"
                + "observed_on INTEGER,"
                + "observed_on_string TEXT,"
                + "time_zone TEXT,"
                + "license TEXT,"
                + "out_of_range INTEGER,"
                + "captive INTEGER,"
                + "place_guess TEXT,"
                + "private_place_guess TEXT,"
                + "uuid TEXT,"
                + "positional_accuracy INTEGER,"
                + "positioning_device TEXT,"
                + "positioning_method TEXT,"
                + "private_latitude REAL,"
                + "private_longitude REAL,"
                + "private_positional_accuracy INTEGER,"
                + "quality_grade TEXT,"
                + "species_guess TEXT,"
                + "preferred_common_name TEXT,"
                + "taxon_id INTEGER,"
                + "time_observed_at INTEGER,"
                + "updated_at INTEGER,"
                + "user_agent TEXT,"
                + "user_id INTEGER,"
                + "user_login TEXT,"
                + "comments_count INTEGER,"
                + "identifications_count INTEGER,"
                + "last_comments_count INTEGER,"
                + "last_identifications_count INTEGER,"
                + "activity_viewed_at INTEGER,"
                + "last_activity_at INTEGER,"
                + "is_deleted INTEGER,"
                + "prefers_community_taxon TEXT DEFAULT NULL,"
                + "owners_identification_from_vision INTEGER,"
                + "scientific_name TEXT,"
                + "rank_level INTEGER,"
                + "rank TEXT"
                + ");";
    }

    public boolean _created_at_changed() { return !String.valueOf(_created_at).equals(String.valueOf(_created_at_was)); }
    public boolean _synced_at_changed() { return !String.valueOf(_synced_at).equals(String.valueOf(_synced_at_was)); }
    public boolean _updated_at_changed() { return !String.valueOf(_updated_at).equals(String.valueOf(_updated_at_was)); }
    public boolean created_at_changed() { return !String.valueOf(created_at).equals(String.valueOf(created_at_was)); }
    public boolean description_changed() { return !String.valueOf(description).equals(String.valueOf(description_was)); }
    public boolean geoprivacy_changed() { return !String.valueOf(geoprivacy).equals(String.valueOf(geoprivacy_was)); }
    public boolean iconic_taxon_id_changed() { return !String.valueOf(iconic_taxon_id).equals(String.valueOf(iconic_taxon_id_was)); }
    public boolean iconic_taxon_name_changed() { return !String.valueOf(iconic_taxon_name).equals(String.valueOf(iconic_taxon_name_was)); }
    public boolean id_changed() { return !String.valueOf(id).equals(String.valueOf(id_was)); }
    public boolean id_please_changed() { return !String.valueOf(id_please).equals(String.valueOf(id_please_was)); }
    public boolean latitude_changed() { return !String.valueOf(latitude).equals(String.valueOf(latitude_was)); }
    public boolean longitude_changed() { return !String.valueOf(longitude).equals(String.valueOf(longitude_was)); }
    public boolean observed_on_changed() { return !String.valueOf(observed_on).equals(String.valueOf(observed_on_was)); }
    public boolean observed_on_string_changed() { return !String.valueOf(observed_on_string).equals(String.valueOf(observed_on_string_was)); }
    public boolean time_zone_changed() { return !String.valueOf(time_zone).equals(String.valueOf(time_zone_was)); }
    public boolean license_changed() { return !String.valueOf(license).equals(String.valueOf(license_was)); }
    public boolean out_of_range_changed() { return !String.valueOf(out_of_range).equals(String.valueOf(out_of_range_was)); }
    public boolean captive_changed() { return !String.valueOf(captive).equals(String.valueOf(captive_was)); }
    public boolean place_guess_changed() { return !String.valueOf(place_guess).equals(String.valueOf(place_guess_was)); }
    public boolean private_place_guess_changed() { return !String.valueOf(private_place_guess).equals(String.valueOf(private_place_guess_was)); }
    public boolean positional_accuracy_changed() { return !String.valueOf(positional_accuracy).equals(String.valueOf(positional_accuracy_was)); }
    public boolean positioning_device_changed() { return !String.valueOf(positioning_device).equals(String.valueOf(positioning_device_was)); }
    public boolean positioning_method_changed() { return !String.valueOf(positioning_method).equals(String.valueOf(positioning_method_was)); }
    public boolean private_latitude_changed() { return !String.valueOf(private_latitude).equals(String.valueOf(private_latitude_was)); }
    public boolean private_longitude_changed() { return !String.valueOf(private_longitude).equals(String.valueOf(private_longitude_was)); }
    public boolean private_positional_accuracy_changed() { return !String.valueOf(private_positional_accuracy).equals(String.valueOf(private_positional_accuracy_was)); }
    public boolean quality_grade_changed() { return !String.valueOf(quality_grade).equals(String.valueOf(quality_grade_was)); }
    public boolean species_guess_changed() { return !String.valueOf(species_guess).equals(String.valueOf(species_guess_was)); }
    public boolean taxon_id_changed() { return !String.valueOf(taxon_id).equals(String.valueOf(taxon_id_was)); }
    public boolean time_observed_at_changed() { return !String.valueOf(time_observed_at).equals(String.valueOf(time_observed_at_was)); }
    public boolean updated_at_changed() { return !String.valueOf(updated_at).equals(String.valueOf(updated_at_was)); }
    public boolean user_agent_changed() { return !String.valueOf(user_agent).equals(String.valueOf(user_agent_was)); }
    public boolean user_id_changed() { return !String.valueOf(user_id).equals(String.valueOf(user_id_was)); }
    public boolean user_login_changed() { return !String.valueOf(user_login).equals(String.valueOf(user_login_was)); }
    public boolean is_deleted_changed() { return is_deleted != is_deleted_was; }
    public boolean prefers_community_taxon_changed() { return prefers_community_taxon != prefers_community_taxon_was; }


    public boolean isDirty() {
        if (_created_at_changed()) { return true; }
        if (_synced_at_changed()) { return true; }
        if (_updated_at_changed()) { return true; }
        if (created_at_changed()) { return true; }
        if (description_changed()) { return true; }
        if (geoprivacy_changed()) { return true; }
        if (iconic_taxon_id_changed()) { return true; }
        if (iconic_taxon_name_changed()) { return true; }
        if (id_changed()) { return true; }
        if (id_please_changed()) { return true; }
        if (latitude_changed()) { return true; }
        if (longitude_changed()) { return true; }
        if (observed_on_changed()) { return true; }
        if (observed_on_string_changed()) { return true; }
        if (time_zone_changed()) { return true; }
        if (license_changed()) { return true; }
        if (out_of_range_changed()) { return true; }
        if (captive_changed()) { return true; }
        if (place_guess_changed()) { return true; }
        if (private_place_guess_changed()) { return true; }
        if (positional_accuracy_changed()) { return true; }
        if (positioning_device_changed()) { return true; }
        if (positioning_method_changed()) { return true; }
        if (private_latitude_changed()) { return true; }
        if (private_longitude_changed()) { return true; }
        if (private_positional_accuracy_changed()) { return true; }
        if (quality_grade_changed()) { return true; }
        if (species_guess_changed()) { return true; }
        if (taxon_id_changed()) { return true; }
        if (time_observed_at_changed()) { return true; }
        if (updated_at_changed()) { return true; }
        if (user_agent_changed()) { return true; }
        if (user_id_changed()) { return true; }
        if (user_login_changed()) { return true; }
        if (is_deleted_changed()) { return true; }
        if (prefers_community_taxon_changed()) { return true; }

        return false;
    }
    
    public Integer updatesCount() {
        Integer idCount = this.identifications_count;
        // assume that one of the IDs is the owner's. Not entirely safe, but often is
        if (this.taxon_id != null && this.taxon_id != 0 && idCount != null && idCount > 0) {
            idCount--;
        }
        Integer c = this.comments_count == null ? 0 : this.comments_count + idCount;
        return c >= 0 ? c : 0;
    }
    
    public boolean unviewedUpdates() {
        Integer c = this.updatesCount();
        if (c == 0) {
            return false;
        }
        return (this.last_comments_count == null) || (this.last_comments_count < this.comments_count) ||
                (this.last_identifications_count == null) || (this.last_identifications_count < this.identifications_count);
    }
}
// END GENERATED BY /Users/kueda/projects/eclipse/workspace/iNaturalist/rails2android.rb AT Mon Jan 09 13:07:06 -0500 2012
