package com.evancharlton.mileage;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import com.evancharlton.mileage.binders.VehicleBinder;
import com.evancharlton.mileage.calculators.CalculationEngine;
import com.evancharlton.mileage.models.FillUp;
import com.evancharlton.mileage.models.Statistic;
import com.evancharlton.mileage.models.StatisticsGroup;
import com.evancharlton.mileage.models.Vehicle;
public class StatisticsView extends TabChildActivity {
private Spinner m_vehicles;
private PreferencesProvider m_preferences;
private CalculationEngine m_calcEngine;
private static final String DATA_GROUP = "group";
private static final String DATA_DONE = "done";
private static final int DIALOG_STATS_PROGRESS = 1;
private static final int MSG_TOTAL = 1;
private static final int MSG_UPDATE = 2;
private static final int MSG_CALCULATING = 3;
private ProgressDialog m_dlg = null;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.statistics);
}
public void onResume() {
super.onResume();
m_preferences = PreferencesProvider.getInstance(this);
m_calcEngine = m_preferences.getCalculator();
initUI();
populateSpinner();
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
Mileage.createMenu(menu);
HelpDialog.injectHelp(menu, 'h');
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
boolean ret = Mileage.parseMenuItem(item, this);
if (ret) {
return true;
}
switch (item.getItemId()) {
case HelpDialog.MENU_HELP:
HelpDialog.create(this, R.string.help_title_statistics, R.string.help_statistics);
break;
}
return super.onOptionsItemSelected(item);
}
private void initUI() {
m_vehicles = (Spinner) findViewById(R.id.stats_vehicle_spinner);
}
private void populateSpinner() {
Cursor c = managedQuery(Vehicle.CONTENT_URI, Vehicle.getProjection(), null, null, Vehicle.DEFAULT_SORT_ORDER);
SimpleCursorAdapter vehicleAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_item, c, new String[] {
Vehicle.TITLE
}, new int[] {
android.R.id.text1
});
vehicleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
vehicleAdapter.setViewBinder(new VehicleBinder());
m_vehicles.setAdapter(vehicleAdapter);
setVehicleSelection(m_vehicles);
m_vehicles.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> adapter, View view, int position, long id) {
updateVehicleSelection(position);
calculateStatistics(m_vehicles.getSelectedItemId());
}
public void onNothingSelected(AdapterView<?> arg0) {
}
});
if (vehicleAdapter.getCount() == 1) {
m_vehicles.setVisibility(View.GONE);
calculateStatistics(vehicleAdapter.getItemId(0));
}
}
private void calculateStatistics(final long id) {
// first, clean up the UI
LinearLayout container = (LinearLayout) findViewById(R.id.stats_container);
container.removeAllViews();
final Handler statsHandler = new Handler() {
public void handleMessage(Message msg) {
Bundle data = msg.getData();
boolean done = data.getBoolean(DATA_DONE, false);
if (!done) {
LinearLayout container = (LinearLayout) findViewById(R.id.stats_container);
StatisticsGroup group = (StatisticsGroup) data.getSerializable(DATA_GROUP);
if (group != null) {
container.addView(group.render(StatisticsView.this));
}
} else {
dismissDialog(DIALOG_STATS_PROGRESS);
m_dlg = null;
}
}
};
final Thread t = new Thread() {
public void run() {
// TODO: optimize this. This has huge overhead
final List<FillUp> fillups = getAllFillUps(id, m_calcEngine);
if (fillups.size() >= 2) {
// crunch the numbers
send(calcDistances(fillups));
send(calcEconomy(fillups));
send(calcPrices(fillups));
send(calcCosts(fillups));
send(calcAmounts(fillups));
send(calcExpenses(fillups));
}
statsHandler.post(new Runnable() {
public void run() {
Bundle data = new Bundle();
data.putBoolean(DATA_DONE, true);
Message msg = new Message();
msg.setData(data);
statsHandler.handleMessage(msg);
}
});
}
private void send(final StatisticsGroup results) {
statsHandler.post(new Runnable() {
public void run() {
Bundle data = new Bundle();
data.putSerializable(DATA_GROUP, results);
Message msg = new Message();
msg.setData(data);
statsHandler.handleMessage(msg);
}
});
}
};
t.start();
showDialog(DIALOG_STATS_PROGRESS);
}
private List<FillUp> getAllFillUps(long id, CalculationEngine engine) {
List<FillUp> all = new ArrayList<FillUp>();
String[] projection = FillUp.getProjection();
String selection = FillUp.VEHICLE_ID + " = ?";
String[] selectionArgs = new String[] {
String.valueOf(id)
};
Cursor c = managedQuery(FillUp.CONTENT_URI, projection, selection, selectionArgs, FillUp.ODOMETER + " ASC");
c.moveToFirst();
final int t = c.getCount();
calculationHandler.post(new Runnable() {
public void run() {
Message msg = new Message();
msg.what = t;
msg.arg1 = MSG_TOTAL;
calculationHandler.sendMessage(msg);
}
});
int i = 0;
FillUp prev = null;
FillUp curr = null;
while (c.isAfterLast() == false) {
curr = new FillUp(engine, c);
all.add(curr);
curr.setPrevious(prev);
if (prev != null) {
prev.setNext(curr);
}
prev = curr;
c.moveToNext();
final int p = ++i;
calculationHandler.post(new Runnable() {
public void run() {
Message msg = new Message();
msg.what = p;
msg.arg1 = MSG_UPDATE;
calculationHandler.sendMessage(msg);
}
});
}
calculationHandler.post(new Runnable() {
public void run() {
Message msg = new Message();
msg.arg1 = MSG_CALCULATING;
calculationHandler.sendMessage(msg);
}
});
return all;
}
@Override
protected Dialog onCreateDialog(int which) {
switch (which) {
case DIALOG_STATS_PROGRESS:
m_dlg = new ProgressDialog(this);
m_dlg.setTitle("Calculating");
m_dlg.setMessage("Calculating statistics...");
m_dlg.setIndeterminate(false);
m_dlg.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
m_dlg.setProgress(0);
return m_dlg;
}
return null;
}
public StatisticsGroup calcDistances(final List<FillUp> fillups) {
StatisticsGroup group = new StatisticsGroup("Distance Between Fill-Ups");
double total_distance = 0D;
double min_distance = Double.MAX_VALUE;
double max_distance = 0D;
for (FillUp fillup : fillups) {
double distance = fillup.calcDistance();
if (distance > 0) {
total_distance += distance;
if (distance < min_distance) {
min_distance = distance;
}
if (distance > max_distance) {
max_distance = distance;
}
}
}
group.add(new Statistic("Average", (total_distance / (fillups.size() - 1)), m_calcEngine.getDistanceUnitsAbbr()));
group.add(new Statistic("Maximum", max_distance, m_calcEngine.getDistanceUnitsAbbr()));
group.add(new Statistic("Minimum", min_distance, m_calcEngine.getDistanceUnitsAbbr()));
group.add(new Statistic("Last", fillups.get(fillups.size() - 1).calcDistance(), m_calcEngine.getDistanceUnitsAbbr()));
return group;
}
public StatisticsGroup calcEconomy(final List<FillUp> fillups) {
StatisticsGroup group = new StatisticsGroup("Fuel Economy");
double total_distance = fillups.get(fillups.size() - 1).getOdometer() - fillups.get(0).getOdometer();
double total_fuel = 0D;
for (int i = fillups.size() - 1; i > 0; i--) {
total_fuel += fillups.get(i).getAmount();
}
double max_economy = 0D;
double min_economy = Double.MAX_VALUE;
if (m_calcEngine.isInverted()) {
max_economy = Double.MAX_VALUE;
min_economy = 0D;
}
for (FillUp fillup : fillups) {
if (!fillup.isPartial()) {
double economy = fillup.calcEconomy();
if (economy < 0) {
continue;
}
if (economy > 0) {
if (m_calcEngine.better(economy, max_economy)) {
max_economy = economy;
}
if (m_calcEngine.worse(economy, min_economy)) {
min_economy = economy;
}
}
}
}
group.add(new Statistic("Average", m_calcEngine.calculateEconomy(total_distance, total_fuel), m_calcEngine.getEconomyUnits()));
group.add(new Statistic("Maximum", max_economy, m_calcEngine.getEconomyUnits()));
group.add(new Statistic("Minimum", min_economy, m_calcEngine.getEconomyUnits()));
group.add(new Statistic("Last", fillups.get(fillups.size() - 1).calcEconomy(), m_calcEngine.getEconomyUnits()));
return group;
}
public StatisticsGroup calcPrices(final List<FillUp> fillups) {
StatisticsGroup group = new StatisticsGroup("Fuel Prices");
double min_price = Double.MAX_VALUE;
double max_price = 0D;
double total_price = 0D;
for (FillUp fillup : fillups) {
double price = fillup.getPrice();
total_price += price;
if (price < min_price) {
min_price = price;
}
if (price > max_price) {
max_price = price;
}
}
group.add(new Statistic("Average", m_preferences.getCurrency(), total_price / fillups.size(), "/" + m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic("Maximum", m_preferences.getCurrency(), max_price, "/" + m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic("Minimum", m_preferences.getCurrency(), min_price, "/" + m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic("Last", m_preferences.getCurrency(), fillups.get(fillups.size() - 1).getPrice(), "/" + m_calcEngine.getVolumeUnitsAbbr()));
return group;
}
public StatisticsGroup calcCosts(final List<FillUp> fillups) {
StatisticsGroup group = new StatisticsGroup("Fill-Up Costs");
double min_cost = Double.MAX_VALUE;
double max_cost = 0D;
double total_cost = 0D;
double min_cost_per_mile = Double.MAX_VALUE;
double max_cost_per_mile = 0D;
for (FillUp fillup : fillups) {
double cost = fillup.calcCost();
total_cost += cost;
if (cost < min_cost) {
min_cost = cost;
}
if (cost > max_cost) {
max_cost = cost;
}
double cost_per_mile = fillup.calcCostPerDistance();
if (cost_per_mile < 0) {
continue;
}
if (cost_per_mile < min_cost_per_mile) {
min_cost_per_mile = cost_per_mile;
}
if (cost_per_mile > max_cost_per_mile) {
max_cost_per_mile = cost_per_mile;
}
}
FillUp last = fillups.get(fillups.size() - 1);
double last_cost = last.calcCost();
double last_cost_per_mile = last.calcCostPerDistance();
double avg_cost = total_cost / fillups.size();
double total_distance = last.getOdometer() - fillups.get(0).getOdometer();
double avg_cost_per_mile = total_cost / total_distance;
String distanceUnits = m_calcEngine.getDistanceUnitsAbbr().trim();
group.add(new Statistic("Average", m_preferences.getCurrency(), avg_cost));
group.add(new Statistic(String.format("Average Cost per %s", distanceUnits), m_preferences.getCurrency(), avg_cost_per_mile));
group.add(new Statistic("Maximum", m_preferences.getCurrency(), max_cost));
group.add(new Statistic(String.format("Maximum cost per %s", distanceUnits), m_preferences.getCurrency(), max_cost_per_mile));
group.add(new Statistic("Minimum", m_preferences.getCurrency(), min_cost));
group.add(new Statistic(String.format("Minimum cost per %s", distanceUnits), m_preferences.getCurrency(), min_cost_per_mile));
group.add(new Statistic("Last", m_preferences.getCurrency(), last_cost));
group.add(new Statistic(String.format("Last Cost per %s", distanceUnits), m_preferences.getCurrency(), last_cost_per_mile));
return group;
}
public StatisticsGroup calcAmounts(final List<FillUp> fillups) {
StatisticsGroup group = new StatisticsGroup("Fuel Amounts");
double min_amount = Double.MAX_VALUE;
double max_amount = 0D;
double total_amount = 0D;
for (FillUp fillup : fillups) {
double amount = fillup.getAmount();
total_amount += amount;
if (amount < min_amount) {
min_amount = amount;
}
if (amount > max_amount) {
max_amount = amount;
}
}
int ten_thousand_miles = (int) Math.ceil(m_calcEngine.convertDistance(PreferencesProvider.MILES, m_calcEngine.getOutputDistance(), 10000));
double distance = fillups.get(fillups.size() - 1).getOdometer() - fillups.get(0).getOdometer();
double fuel_per_10k = (m_calcEngine.convertVolume(total_amount) / m_calcEngine.convertDistance(distance)) * ten_thousand_miles;
group.add(new Statistic("Total", total_amount, m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic("Average", total_amount / fillups.size(), m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic("Maximum", max_amount, m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic("Minimum", min_amount, m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic("Last", fillups.get(fillups.size() - 1).calcCost(), m_calcEngine.getVolumeUnitsAbbr()));
group.add(new Statistic(String.format("Fuel / %d %s", ten_thousand_miles, m_calcEngine.getDistanceUnitsAbbr()), fuel_per_10k, m_calcEngine.getVolumeUnitsAbbr()));
return group;
}
public StatisticsGroup calcExpenses(final List<FillUp> fillups) {
StatisticsGroup group = new StatisticsGroup("Fuel Expenses");
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(System.currentTimeMillis());
long thirty_days_ago = new GregorianCalendar(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) - 1, cal.get(Calendar.DAY_OF_MONTH)).getTimeInMillis();
long one_year_ago = new GregorianCalendar(cal.get(Calendar.YEAR) - 1, cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).getTimeInMillis();
double thirty_day_total = 0D;
double yearly_total = 0D;
double total = 0D;
for (FillUp fillup : fillups) {
total += fillup.calcCost();
long time = fillup.getDate().getTimeInMillis();
if (time > thirty_days_ago) {
thirty_day_total += fillup.calcCost();
}
if (time > one_year_ago) {
yearly_total += fillup.calcCost();
}
}
group.add(new Statistic("Total", m_preferences.getCurrency(), total));
group.add(new Statistic("Last Month", m_preferences.getCurrency(), thirty_day_total));
group.add(new Statistic("Last Year", m_preferences.getCurrency(), yearly_total));
return group;
}
private Handler calculationHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.arg1 == MSG_TOTAL) {
if (m_dlg != null) {
m_dlg.setMax(msg.what);
}
} else if (msg.arg1 == MSG_UPDATE) {
if (m_dlg != null) {
m_dlg.setProgress(msg.what);
}
} else if (msg.arg1 == MSG_CALCULATING) {
if (m_dlg != null) {
m_dlg.setIndeterminate(true);
}
}
}
};
}