package edu.mit.mitmobile2.shuttles;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher;
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher.OnRefreshListener;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import edu.mit.mitmobile2.Global;
import edu.mit.mitmobile2.LoaderBar;
import edu.mit.mitmobile2.LockingScrollView;
import edu.mit.mitmobile2.MobileWebApi;
import edu.mit.mitmobile2.R;
import edu.mit.mitmobile2.SliderInterface;
import edu.mit.mitmobile2.alerts.NotificationsAlarmReceiver;
import edu.mit.mitmobile2.objs.Predicted;
import edu.mit.mitmobile2.objs.RouteItem;
import edu.mit.mitmobile2.objs.RouteItem.Stops;
import edu.mit.mitmobile2.shuttles.ShuttleRouteArrayAdapter.SectionListItemView;
public class StopsAsyncView extends LinearLayout implements SliderInterface , OnItemClickListener, OnRefreshListener {
ArrayList<Stops> m_stops;
MITStopsSliderActivity top;
CheckStopsTask stopsTask;
TextView routeTV;
ListView stopsLV;
ShuttleRouteArrayAdapter adapter;
HashMap <String,Predicted> alert_pis = new HashMap <String,Predicted>();
LoaderBar lb;
Stops si;
Context ctx;
private PullToRefreshAttacher mRefreshAttacher;
/****************************************************/
class CheckStopsTask extends AsyncTask<String, Void, Void> {
StopsParser sp;
boolean firstTime = true;
@Override
protected Void doInBackground(String... urls) {
String url = urls[0];
while(true) {
// Update stops...
sp = new StopsParser();
sp.getJSON(url,true);
// Update bcos Notification service may have changed alerts
MITStopsSliderActivity.alertIdx = ShuttleModel.getAlerts(top.pref);
if (isCancelled()) {
return null; //FIXME this should not be necessary but cancel() doesn't end this loop
}
publishProgress ((Void)null);
// Sleep...
try {
Thread.sleep(1000*30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@SuppressWarnings("unchecked")
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
lb.setLastLoaded(new Date());
lb.endLoading();
mRefreshAttacher.setRefreshComplete();
boolean no_data = false;
if (sp==null) no_data = true;
else if (sp.items.size() == 0) no_data = true;
if (no_data) {
if(m_stops.size() == 0) {
Toast.makeText(ctx, MobileWebApi.NETWORK_ERROR, Toast.LENGTH_LONG).show();
lb.errorLoading();
}
return;
}
m_stops = (ArrayList<Stops>) sp.items;
if (!m_stops.isEmpty()) {
Stops s;
// Initialize...
if (firstTime) {
SectionListItemView itemBuilder = new SectionListItemView() {
@Override
public View getView(Object item, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.stops_row, null);
}
Predicted s = (Predicted) item;
// Time
TextView timeTV = (TextView) v.findViewById(R.id.stopsRowTimeTV);
timeTV.setTextColor(0xFF000000);
long curTime = System.currentTimeMillis();
Date d = new Date();
d.setTime(s.next*1000);
SimpleDateFormat df = new SimpleDateFormat("h:mm a");
String formatted = df.format(d);
timeTV.setText(formatted);
////////////
// Mins
long mins = (s.next*1000 - curTime)/1000/60;
long hours = mins / 60;
String text = null;
if (hours>1) {
mins = mins - (hours*60);
text = "(" + String.valueOf(hours) + " hrs " + String.valueOf(mins) + " mins)";
} else if (hours>0) {
mins = mins - (hours*60);
text = "(" + String.valueOf(hours) + " hr " + String.valueOf(mins) + " mins)";
} else {
if (mins==0) text = "(now)";
else if (mins==1) text = "(1 min)";
else text = "(" + String.valueOf(mins) + " mins)";
}
TextView minsTV = (TextView) v.findViewById(R.id.stopsRowMinsTV);
minsTV.setText(text);
////////////
// Alarm
ImageView routeIV = (ImageView) v.findViewById(R.id.stopsRowIV);
if (s.showAlert) {
routeIV.setVisibility(View.VISIBLE);
if (s.alertSet) {
routeIV.setImageResource(R.drawable.shuttle_alert_toggle_on);
} else {
routeIV.setImageResource(R.drawable.shuttle_alert_toggle_off);
}
} else {
routeIV.setVisibility(View.INVISIBLE);
}
return v;
}
};
// TODO rename ShuttleRouteArrayAdapter to something more generic
adapter = new ShuttleRouteArrayAdapter(ctx, itemBuilder);
firstTime = false;
}
adapter.clear();
HashMap<String, ArrayList<Predicted>> sections = new HashMap<String, ArrayList<Predicted>>();
// Update
Predicted pi;
Predicted prev_pi;
long curTime = System.currentTimeMillis();
for (int x=0; x<m_stops.size(); x++) {
s = m_stops.get(x);
if (!sections.containsKey(s.route_id)) {
sections.put(s.route_id, new ArrayList<Predicted>());
}
ArrayList<Predicted> predictions = sections.get(s.route_id);
Log.d("StopsAsyncView",""+ s.toString());
// first stop...
pi = new Predicted();
pi.next = s.next;
pi.stop_id = s.id;
pi.route_id = s.route_id;
pi.showAlert = false;
if (s.next*1000>curTime+5*60*1000) {
pi.showAlert = true;
}
predictions.add(pi);
// is there an alarm for this route?
HashMap<String, Long> routes_times = MITStopsSliderActivity.alertIdx.get(s.id); // TODO move out of loop
Long alert_time = null;
if (routes_times==null) {
Log.d("StopsAsyncView", "shuttle-alerts: routes_times null for "+s.id);
} else {
alert_time = routes_times.get(s.route_id);
}
boolean found_alert = false;
if (alert_time==null) {
alert_time = new Long(-1);
found_alert = true; // prevents marking "Alarm set"
} else {
// delete past alerts (being safe - should have been updated by NotificationService)
if (System.currentTimeMillis() - ShuttleModel.ALERT_EXPIRE_TIME > alert_time) {
routes_times.remove(s.route_id);
if (routes_times.isEmpty()) MITStopsSliderActivity.alertIdx.remove(s.id);
else MITStopsSliderActivity.alertIdx.put(s.id,routes_times);
ShuttleModel.saveAlerts(top.pref, MITStopsSliderActivity.alertIdx);
alert_time = new Long(-1);
found_alert = true;
}
Log.d("StopsAsyncView", "shuttle-alert: update=> alert_time="+alert_time);
}
// the rest...
prev_pi = pi;
Integer p;
for (int z=0; z<s.predictions.size(); z++) {
p = s.predictions.get(z);
pi = new Predicted();
pi.next = s.now + p.longValue();
pi.stop_id = s.id;
pi.route_id = s.route_id;
pi.alertSet = false;
pi.showAlert = true;
predictions.add(pi);
// Alert time passed?
if (((pi.next*1000)>alert_time)&&(!found_alert)) {
found_alert = true;
prev_pi.alertSet = true;
alert_pis.put(s.route_id, prev_pi);
}
prev_pi = pi;
}
if (!found_alert) {
prev_pi.alertSet = true;
alert_pis.put(s.route_id, prev_pi);
}
} // for stops
// TODO move this somewhere less transient than this
HashMap<String, String> routeTitles = new HashMap<String, String>();
for (RouteItem aRouteItem : ShuttleModel.getSortedRoutes()) {
routeTitles.put(aRouteItem.route_id, aRouteItem.title);
}
// Add current route first
String routeTitle;
ArrayList<Predicted> c = sections.get(top.routeId);
if (c!=null) {
routeTitle = routeTitles.get(top.routeId);
if (routeTitle == null) routeTitle = top.routeId;
sections.remove(top.routeId);
adapter.addSection(routeTitle, c);
}
// Now add remainder...
for (Entry<String, ArrayList<Predicted>> entry: sections.entrySet()) {
routeTitle = routeTitles.get(entry.getKey());
if (routeTitle == null) routeTitle = entry.getKey();
adapter.addSection(routeTitle, entry.getValue());
}
stopsLV.setOnItemClickListener(StopsAsyncView.this);
stopsLV.setAdapter(adapter);
} // isEmpty
} // progressUpdate
} // class CheckStopsTask
/****************************************************/
void terminate() {
if (stopsTask!=null) {
boolean isCanceled;
isCanceled = stopsTask.cancel(true);
while (!isCanceled) {
// Sleep...
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
isCanceled = stopsTask.cancel(true);
}
stopsTask = null;
}
}
/**
* @param stops **************************************************/
public StopsAsyncView(Context context, Stops stops) {
super(context);
ctx = context;
si = stops;
top = (MITStopsSliderActivity) context;
LayoutInflater vi = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout topView = (LinearLayout) vi.inflate(R.layout.stops, null);
stopsLV = (ListView) topView.findViewById(R.id.stopsLV);
TextView titleTV = (TextView) topView.findViewById(R.id.stopsTitleTV);
titleTV.setText(si.title);
// FIXME this will break on screen rotation
// FIXME HACK!!! neither FILL not layout_weight=1 with WRAP work
Display display = top.getWindowManager().getDefaultDisplay();
int height = display.getHeight();
topView.setMinimumHeight(height-30);
lb = new LoaderBar(ctx);
topView.addView(lb, 0);
addView(topView);
mRefreshAttacher = top.createPullToRefreshAttacher();
mRefreshAttacher.setRefreshableView(stopsLV, this);
}
/****************************************************/
void getData() {
m_stops = new ArrayList<Stops>();
if (stopsTask!=null) {
if (!stopsTask.isCancelled()) {
stopsTask.cancel(true);
//throw new RuntimeException("should have been canceled");
}
}
stopsTask = new CheckStopsTask();
RoutesParser rp = new RoutesParser();
stopsTask.execute(rp.getBaseUrl()+"?command=stopInfo&id="+si.id, null, null);
}
/****************************************************/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ListView l = (ListView) parent;
//ShuttleRouteArrayAdapter sa = (ShuttleRouteArrayAdapter) l.getAdapter();
Predicted p = (Predicted) l.getItemAtPosition(position);
Predicted alert_pi = alert_pis.get(p.route_id);
long curTime = System.currentTimeMillis();
if ((p.next*1000)<(curTime+5*60*1000)) return;
AlarmManager alarmManager = (AlarmManager) ctx.getSystemService(Service.ALARM_SERVICE);
// Three cases:
//
// - setting new, no previous
// - setting new, clear different previous
// - clearing previous
// Is there an existing alert?
HashMap<String,Long> routes_times = MITStopsSliderActivity.alertIdx.get(si.id);
Long alertTime = null;
if (routes_times==null) {
routes_times = new HashMap<String,Long>();
MITStopsSliderActivity.alertIdx.put(si.id, routes_times);
} else {
alertTime = routes_times.get(p.route_id);
}
if (alertTime!=null) {
// Yes - remove previous alert
routes_times.remove(p.route_id);
if (routes_times.isEmpty()) {
MITStopsSliderActivity.alertIdx.remove(si.id);
}
if (alert_pi!=null) {
// Cancel (needs to match previous one)
Intent i = new Intent(ctx, NotificationsAlarmReceiver.class);
i.setAction(NotificationsAlarmReceiver.ACTION_ALARM_SHUTTLE);
Uri data = Uri.parse(alert_pi.stop_id + alert_pi.route_id);
i.setData(data);
PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 0, i, 0);
alarmManager.cancel(pendingIntent);
Log.d("StopsAsyncView", "shuttle-alerts: cancel: " + alert_pi.stop_id + " " + alert_pi.route_id);
if (p!=alert_pi) alert_pi.alertSet = false;
alert_pi = null;
alert_pis.put(p.route_id, null);
}
}
//
// >>>>>> Alert times holds EXACT alert times while we schedule Alarms slightly in advance of this
//
// Cancel or Schedule alarm?
if (p.alertSet) {
/*
alert_pi = null;
// Cancel (needs to match previous one)
Intent i = new Intent(ctx, NotificationsAlarmReceiver.class);
i.setAction(NotificationsAlarmReceiver.ACTION_ALARM_SHUTTLE);
Uri data = Uri.parse(p.stop_id + p.route_id);
i.setData(data);
PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 0, i, 0);
alarmManager.cancel(pendingIntent);
*/
} else {
// Schedule new alert
alert_pi = p;
alert_pis.put(p.route_id, p);
long wakeTime;
long busTime = p.next*1000;
long diff = busTime - curTime;
// add to map
alertTime = new Long(busTime);
routes_times.put(p.route_id, alertTime);
MITStopsSliderActivity.alertIdx.put(si.id,routes_times);
// set wakeup time
if (diff<5*60*1000) {
// ignore if under 5 mins...
return;
} else if (diff<30*60*1000) {
wakeTime = busTime - 5*60*1000; // wake up 5 mins before...
} else {
wakeTime = busTime - 20*60*1000; // wake up 20 mins before...
}
if (Global.DEBUG) {
wakeTime = curTime + 30*1000; // TODO DEBUG
//busTime = curTime + 30*1000;
busTime = curTime + 7*60*1000;
}
String title = si.title;
if (title==null) {
title = "unknown stop";
}
// FIXME need to differentiate alarms
Log.d("StopsAsyncView", "shuttle-alerts: set: " + alert_pi.stop_id + " @ " + alert_pi.route_id);
Intent i = new Intent(ctx, NotificationsAlarmReceiver.class);
i.setAction(NotificationsAlarmReceiver.ACTION_ALARM_SHUTTLE);
Uri data = Uri.parse(p.stop_id + p.route_id);
i.setData(data);
i.putExtra(ShuttleModel.KEY_STOP_TITLE, title);
i.putExtra(ShuttleModel.KEY_STOP_ID, p.stop_id);
i.putExtra(ShuttleModel.KEY_ROUTE_ID, p.route_id);
i.putExtra(ShuttleModel.KEY_TIME, busTime);
PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 0, i, 0);
alarmManager.set(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
}
p.alertSet = !p.alertSet;
//l.invalidate();
l.postInvalidate();
/*
// TODO checks
int count = l.getCount();
int num_set = 0;
for (int i=0; i<count; i++) {
p = (Predicted) l.getItemAtPosition(i);
if (p.alertSet) num_set++;
if (num_set>1) Log.e("StopsAsyncView", "StopsAsyncView: too many alerts i=" + i);
}
*/
ShuttleModel.saveAlerts(top.pref,MITStopsSliderActivity.alertIdx); // TODO better?
adapter.notifyDataSetChanged();
//sa.notifyDataSetChanged();
}
@Override
public View getView() {
return this;
}
@Override
public void updateView() {
//if (!updateThreadRunning) getData();
}
@Override
public void onSelected() {
if (stopsTask==null) {
lb.startLoading();
getData();
}
}
@Override
public LockingScrollView getVerticalScrollView() {
return null;
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
}
@Override
public void onRefreshStarted(View view) {
getData();
}
}