public class ConnectionService {

    private final EventBus eventBus;
    private final BackendService backendService;

    @Inject
    public ConnectionService(EventBus eventBus, BackendService backendService) {
        this.backendService = backendService;
        this.eventBus = eventBus;
        this.eventBus.register(this);
    }

    @Subscribe
    public void onEvent(FetchConnectionsEvent event) {

        backendService.getConnections(
                String.valueOf(event.getStationId()),
                String.valueOf(event.getDestinationId()),
                event.getDepartureString(),
                event.getArrivalString())
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ch.liip.timeforcoffee.backend.Connections>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        eventBus.post(new FetchConnectionsErrorEvent(e));
                    }

                    @Override
                    public void onNext(ch.liip.timeforcoffee.backend.Connections backendConnections) {
                        List<Connection> connections = new ArrayList<>();
                        List<List<ch.liip.timeforcoffee.backend.Connection>> backendConnectionsList = backendConnections.getConnections();
                        if (backendConnectionsList.size() > 0) {
                            for (ch.liip.timeforcoffee.backend.Connection backendConnection : backendConnectionsList.get(0)) {
                                connections.add(ConnectionMapper.fromBackend(backendConnection));
                            }
                        }

                        eventBus.post(new ConnectionsFetchedEvent(connections));
                    }
                });
    }
} public class DepartureService {

    private final EventBus eventBus;
    private final BackendService backendService;

    @Inject
    public DepartureService(EventBus eventBus, BackendService backendService) {
        this.backendService = backendService;
        this.eventBus = eventBus;
        this.eventBus.register(this);
    }

    @Subscribe
    public void onEvent(FetchDeparturesEvent event) {

        backendService.getDepartures(String.valueOf(event.getStationId()))
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ch.liip.timeforcoffee.backend.Departures>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        eventBus.post(new FetchDeparturesErrorEvent(e));
                    }

                    @Override
                    public void onNext(ch.liip.timeforcoffee.backend.Departures backendDepartures) {
                        ArrayList<Departure> departures = new ArrayList<>();
                        for (ch.liip.timeforcoffee.backend.Departure backendDeparture : backendDepartures.getDepartures()) {
                            departures.add(DepartureMapper.fromBackend(backendDeparture));
                        }

                        eventBus.post(new DeparturesFetchedEvent(departures));
                    }
                });

    }
} public class StationService {

    private final EventBus eventBus;
    private final OpenDataService openDataService;

    @Inject
    public StationService(EventBus eventBus, OpenDataService openDataService) {
        this.openDataService = openDataService;
        this.eventBus = eventBus;
        this.eventBus.register(this);
    }

    @Subscribe
    public void onEvent(FetchStationsSearchEvent event) {
        Map<String, String> query = new HashMap<>();
        query.put("type", "station");
        query.put("query", event.getSearchQuery());

        openDataService.getStations(query)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ch.liip.timeforcoffee.backend.Stations>() {
                    @Override
                    public void onCompleted() { }

                    @Override
                    public void onError(Throwable e) {
                        eventBus.post(new FetchStationsSearchErrorEvent(e));
                    }

                    @Override
                    public void onNext(ch.liip.timeforcoffee.backend.Stations backendStations) {
                        ArrayList<Station> stations = new ArrayList<>();
                        for (ch.liip.timeforcoffee.backend.Station backendStation : backendStations.getStations()) {
                            stations.add(StationMapper.fromBackend(backendStation));
                        }

                        eventBus.post(new StationsSearchFetchedEvent(stations));
                    }
                });
    }

    @Subscribe
    public void onEvent(FetchStationsLocationEvent event) {
        Map<String, String> query = new HashMap<>();
        query.put("type", "station");
        query.put("x", Double.toString(event.getX()));
        query.put("y", Double.toString(event.getY()));

        openDataService.getStations(query)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ch.liip.timeforcoffee.backend.Stations>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        eventBus.post(new FetchStationsLocationErrorEvent(e));
                    }

                    @Override
                    public void onNext(ch.liip.timeforcoffee.backend.Stations backendStations) {
                        ArrayList<Station> stations = new ArrayList<>();
                        for (ch.liip.timeforcoffee.backend.Station backendStation : backendStations.getStations()) {
                            if (backendStation.getId() == 0) {
                                //the API returns a station with null id corresponding to current location, we exclude it
                                continue;
                            }
                            stations.add(StationMapper.fromBackend(backendStation));
                        }

                        eventBus.post(new StationsLocationFetchedEvent(stations));
                    }
                });
    }
} public class DateDeserializer implements JsonDeserializer<Date> {

  private final String[] DATE_FORMATS = new String[] { "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ssZ" };

  @Override
  public Date deserialize(JsonElement jsonElement, Type typeOF, JsonDeserializationContext context) throws JsonParseException {
    for (String format : DATE_FORMATS) {
      try {
        return new SimpleDateFormat(format).parse(jsonElement.getAsString());
      } catch (ParseException e) { }
    }

    throw new JsonParseException("Unparseable date: \"" + jsonElement.getAsString() + "\". Supported formats: " + Arrays.toString(DATE_FORMATS));
  }
} public interface BackendService {

    @Headers("Cache-Control:public, max-age=20")
    @GET("/api/ch/stationboard/{stationId}")
    Observable<Departures> getDepartures(@Path("stationId") String stationId);

    @Headers("Cache-Control:public, max-age=20")
    @GET("/api/ch/connections/{stationId}/{destinationId}/{departureDate}/{arrivalDate}")
    Observable<Connections> getConnections(
            @Path("stationId") String stationId,
            @Path("destinationId") String destinationId,
            @Path("departureDate") String departureDate,
            @Path("arrivalDate") String arrivalDate);
} public interface OpenDataService {

    @Headers("Cache-Control:public, max-age=20")
    @GET("/v1/locations")
    Observable<Stations> getStations(@QueryMap Map<String, String> query);
} public class DepartureListFragment extends Fragment {

    private Station mStation;

    private ProgressBar mProgressBar;
    private TextView mNoResultsTextView;
    private TextView mTitleTextView;
    private ListView mListView;
    private DepartureListAdapter mAdapter;

    private List<Departure> mDepartures = new ArrayList<>();

    public DepartureListFragment() {
        // Required empty public constructor
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_departure_list, container, false);

        mProgressBar = view.findViewById(R.id.progressBar);
        mNoResultsTextView = view.findViewById(R.id.noResults);
        mTitleTextView = view.findViewById(R.id.title);
        mListView = view.findViewById(R.id.list_view);

        mAdapter = new DepartureListAdapter(getActivity(), mDepartures);
        mListView.setAdapter(mAdapter);

        return view;
    }

    public void setStation(Station station) {
        mStation = station;
        mTitleTextView.setText(station.getName());
    }

    public Station getStation() {
        return mStation;
    }

    public void setDepartures(List<Departure> departures) {
        mDepartures = departures;
    }

    public void displayDepartures() {
        mProgressBar.setVisibility(View.GONE);
        mNoResultsTextView.setVisibility(View.GONE);
        mTitleTextView.setVisibility(View.VISIBLE);
        mListView.setVisibility(View.VISIBLE);

        if (mAdapter != null && mDepartures != null) {
            mAdapter.setDepartures(mDepartures);
        }
    }

    public void displayProgressIndicator() {
        mProgressBar.setVisibility(View.VISIBLE);
        mNoResultsTextView.setVisibility(View.GONE);
        mTitleTextView.setVisibility(View.GONE);
        mListView.setVisibility(View.GONE);
    }

    public void displayNoResults() {
        mProgressBar.setVisibility(View.GONE);
        mNoResultsTextView.setVisibility(View.VISIBLE);
        mTitleTextView.setVisibility(View.GONE);
        mListView.setVisibility(View.GONE);
    }
} public class FavoritesListFragment extends ListFragment implements SwipeRefreshLayout.OnRefreshListener, StationListAdapter.Callbacks, DepartureListAdapter.Callbacks {

    public static final String ARG_MODE = "favorite_mode";
    public static final int ARG_MODE_STATIONS = 0;
    public static final int ARG_MODE_DEPARTURES = 1;
    private int mFavoriteMode;

    private StationListAdapter mStationListAdapter;
    private DepartureListAdapter mDepartureListAdapter;
    private LinearLayout mNoFavoritesLayout;
    private SwipeRefreshLayout mSwipeRefreshLayout;

    private Callbacks mCallbacks;

    public interface Callbacks {
        void onFavoriteStationSelected(Station station);
        void onFavoriteDepartureSelected(Departure departure);
        void onStationFavoriteToggled(Station station, boolean isFavorite);
        void onDepartureFavoriteToggled(Departure station, boolean isFavorite);
    }

    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public FavoritesListFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ((TimeForCoffeeApplication) getActivity().getApplication()).inject(this);

        Bundle args = getArguments();
        if(args == null) {
            return;
        }

        mFavoriteMode = args.getInt(ARG_MODE);
        if(mFavoriteMode == FavoritesListFragment.ARG_MODE_STATIONS) {
            if (mStationListAdapter == null) {
                mStationListAdapter = new StationListAdapter(getActivity(), new ArrayList<Station>(), this);
            }
            setListAdapter(mStationListAdapter);
        }
        else {
            mDepartureListAdapter = new DepartureListAdapter(getActivity(), new ArrayList<Departure>(), this);
            setListAdapter(mDepartureListAdapter);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_favorites_list, container, false);

        mNoFavoritesLayout = rootView.findViewById(R.id.noFavoritesLayout);
        mSwipeRefreshLayout = rootView.findViewById(R.id.swipe_container);
        mSwipeRefreshLayout.setOnRefreshListener(this);
        showNoFavoritesLayout(mStationListAdapter == null || mStationListAdapter.getCount() == 0);

        return rootView;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (!(context instanceof Callbacks)) {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }

        mCallbacks = (Callbacks) context;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = null;
    }

    @Override
    public void onRefresh() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mSwipeRefreshLayout.setRefreshing(false);
            }
        }, 100);
    }

    @Override
    public void onListItemClick(ListView listView, View view, int position, long id) {
        super.onListItemClick(listView, view, position, id);
        if(mCallbacks != null) {
            if(mFavoriteMode == FavoritesListFragment.ARG_MODE_STATIONS) {
                mCallbacks.onFavoriteStationSelected(mStationListAdapter.getStation(position));
            }
            else {
                mCallbacks.onFavoriteDepartureSelected(mDepartureListAdapter.getDeparture(position));
            }
        }
    }

    @Override
    public void onStationFavoriteToggled(Station station, boolean isFavorite) {
        if(mCallbacks != null) {
            mCallbacks.onStationFavoriteToggled(station, isFavorite);
        }
        showNoFavoritesLayout(mStationListAdapter.getCount() == 0);
    }

    @Override
    public void onDepartureFavoriteToggled(Departure departure, boolean isFavorite) {
        if(mCallbacks != null) {
            mCallbacks.onDepartureFavoriteToggled(departure, isFavorite);
        }
    }

    public void setStations(List<Station> stations) {
        showNoFavoritesLayout(stations.size() == 0);
        mStationListAdapter.setStations(stations);
    }

    public void setDepartures(List<Departure> departures) {
        showNoFavoritesLayout(departures.size() == 0);
        mDepartureListAdapter.setDepartures(departures);
    }

    private void showNoFavoritesLayout(boolean show) {
        if (show) {
            mNoFavoritesLayout.setVisibility(View.VISIBLE);
        } else {
            mNoFavoritesLayout.setVisibility(View.GONE);
        }
    }
} public class StationListFragment extends ListFragment implements SwipeRefreshLayout.OnRefreshListener, StationListAdapter.Callbacks {

    public static final String ARG_SEARCH_MODE = "search_mode";
    private boolean mSearchMode;

    private FragmentActivity mActivity;
    private StationListAdapter mStationListAdapter;
    private RelativeLayout mNoStationsLayout;
    private RelativeLayout mLoadingPositionLayout;
    private RelativeLayout mEnterSearchLayout;
    private SwipeRefreshLayout swipeRefreshLayout;

    private Callbacks mCallbacks;

    @Inject
    FavoritesDataSource favoritesDataSource;

    public interface Callbacks {
        void onRefresh();
        void onStationSelected(Station station);
        void onStationFavoriteToggled(Station station, boolean isFavorite);
    }

    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public StationListFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ((TimeForCoffeeApplication) getActivity().getApplication()).inject(this);

        mActivity = getActivity();
        Bundle args = getArguments();

        if(args == null) {
            return;
        }

        mSearchMode = args.getBoolean(ARG_SEARCH_MODE);
        mStationListAdapter = new StationListAdapter(getActivity(), new ArrayList<Station>(), this);
        setListAdapter(mStationListAdapter);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_station_list, container, false);

        mLoadingPositionLayout = rootView.findViewById(R.id.loadingPositionLayout);
        mNoStationsLayout = rootView.findViewById(R.id.noStationsLayout);
        mEnterSearchLayout = rootView.findViewById(R.id.enterSearchLayout);
        swipeRefreshLayout = rootView.findViewById(R.id.swipe_container);
        swipeRefreshLayout.setOnRefreshListener(this);

        mLoadingPositionLayout.setVisibility(View.GONE);
        mNoStationsLayout.setVisibility(View.GONE);
        if (mSearchMode) {
            mEnterSearchLayout.setVisibility(View.VISIBLE);
        } else {
            mEnterSearchLayout.setVisibility(View.GONE);
        }

        return rootView;
    }

    @Override
    public void onResume() {
        super.onResume();
        if(mActivity instanceof MainActivity) {
            ((MainActivity)mActivity).performStationsUpdate();
        }
        onRefresh();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (!(context instanceof Callbacks)) {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }

        mCallbacks = (Callbacks) context;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = null;
    }

    @Override
    public void onRefresh() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                swipeRefreshLayout.setRefreshing(false);
                if(mCallbacks != null) {
                    mCallbacks.onRefresh();
                }
            }
        }, 100);
    }

    @Override
    public void onListItemClick(ListView listView, View view, int position, long id) {
        super.onListItemClick(listView, view, position, id);
        if(mCallbacks != null) {
            mCallbacks.onStationSelected(mStationListAdapter.getStation(position));
        }
    }

    @Override
    public void onStationFavoriteToggled(Station station, boolean isFavorite) {
        if(mCallbacks != null) {
            mCallbacks.onStationFavoriteToggled(station, isFavorite);
        }
    }

    public void setStations(List<Station> stations) {
        if(mSearchMode) {
            showNoStationsLayout(stations.size() == 0);
        }

        if (mEnterSearchLayout != null || mStationListAdapter != null) {
            mEnterSearchLayout.setVisibility(View.GONE);
            mStationListAdapter.setStations(stations);
        }
    }

    public void showLoadingPositionLayout(boolean show) {
        if(mLoadingPositionLayout != null) {
            if (show) {
                mLoadingPositionLayout.setVisibility(View.VISIBLE);
            }
            else {
                mLoadingPositionLayout.setVisibility(View.GONE);
            }
        }

    }

    private void showNoStationsLayout(boolean show) {
        if(mNoStationsLayout != null && mEnterSearchLayout != null) {
            mEnterSearchLayout.setVisibility(View.GONE);
            if (show) {
                mNoStationsLayout.setVisibility(View.VISIBLE);
            }
            else {
                mNoStationsLayout.setVisibility(View.GONE);
            }
        }
    }
} public class FavoritesDataSource {

    private SQLiteDatabase database;

    private String[] allStationColumns = {FavoriteStationColumn.COLUMN_ID, FavoriteStationColumn.COLUMN_STATION_ID,
            FavoriteStationColumn.COLUMN_NAME, FavoriteStationColumn.COLUMN_LATITUDE, FavoriteStationColumn.COLUMN_LONGITUDE};

    private String[] allLineColumns = {FavoriteLineColumn.COLUMN_ID, FavoriteLineColumn.COLUMN_NAME, FavoriteLineColumn.COLUMN_DESTINATION_ID};

    public void insertFavoriteStation(Context context, Station station) {
        open(context);

        ContentValues values = new ContentValues();
        values.put(FavoriteStationColumn.COLUMN_STATION_ID, station.getId());
        values.put(FavoriteStationColumn.COLUMN_NAME, station.getName());
        values.put(FavoriteStationColumn.COLUMN_LATITUDE, station.getLocation().getLatitude());
        values.put(FavoriteStationColumn.COLUMN_LONGITUDE, station.getLocation().getLongitude());
        database.insert(FavoriteStationColumn.TABLE_NAME, null, values);

        close();
    }

    public void deleteFavoriteStation(Context context, Station station) {
        open(context);
        database.delete(FavoriteStationColumn.TABLE_NAME, FavoriteStationColumn.COLUMN_STATION_ID + " = " + station.getId(), null);

        close();
    }

    public void insertFavoriteLine(Context context, Departure departure) {
        open(context);

        ContentValues values = new ContentValues();
        values.put(FavoriteLineColumn.COLUMN_NAME, departure.getLine());
        values.put(FavoriteLineColumn.COLUMN_DESTINATION_ID, departure.getDestinationId());
        database.insert(FavoriteLineColumn.TABLE_NAME, null, values);

        close();
    }

    public void deleteFavoriteLine(Context context, Departure departure) {
        open(context);
        database.delete(FavoriteLineColumn.TABLE_NAME, FavoriteLineColumn.COLUMN_NAME + " = '" + departure.getLine()  + "' AND " +
                FavoriteLineColumn.COLUMN_DESTINATION_ID + " = " + departure.getDestinationId(), null);

        close();
    }

    public List<Station> getAllFavoriteStations(Context context) {
        open(context);

        List<Station> stations = new ArrayList<>();
        Cursor cursor = database.query(FavoriteStationColumn.TABLE_NAME, allStationColumns, null, null, null, null, null);
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            Station station = cursorToStation(cursor);
            stations.add(station);
            cursor.moveToNext();
        }

        cursor.close();
        close();

        return stations;
    }

    public List<Departure> getAllFavoriteLines(Context context) {
        open(context);

        List<Departure> departures = new ArrayList<>();
        Cursor cursor = database.query(FavoriteLineColumn.TABLE_NAME, allLineColumns, null, null, null, null, null);
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            Departure departure = cursorToLine(cursor);
            departures.add(departure);
            cursor.moveToNext();
        }

        cursor.close();
        close();

        return departures;
    }

    private void open(Context context) throws SQLException {
        database = new FavoritesDatabaseHelper(context).getWritableDatabase();
    }

    private void close() {
        if (database != null && database.isOpen()) {
            database.close();
        }
    }

    private Station cursorToStation(Cursor cursor) {
        int id = cursor.getInt(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_STATION_ID));
        String name = cursor.getString(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_NAME));
        double latitude = cursor.getDouble(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_LATITUDE));
        double longitude = cursor.getDouble(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_LONGITUDE));

        Location location = new Location("fav");
        location.setLatitude(latitude);
        location.setLongitude(longitude);

        return new Station(id, name, 0, location, true);
    }

    private Departure cursorToLine(Cursor cursor) {
        int destinationId = cursor.getInt(cursor.getColumnIndexOrThrow(FavoriteLineColumn.COLUMN_DESTINATION_ID));
        String line = cursor.getString(cursor.getColumnIndexOrThrow(FavoriteLineColumn.COLUMN_NAME));

        return new Departure(destinationId, line, true);
    }
} public class ConnectionsPresenter implements Presenter {

    private ConnectionsActivity mActivity;
    private Station mStation;
    private Departure mDeparture;
    private List<Connection> mConnections;

    @Inject
    EventBus mEventBus;

    @Inject
    ConnectionService connectionService;

    @Inject
    BackendService backendService;

    @Inject
    FavoritesDataSource favoritesDataSource;

    public ConnectionsPresenter(ConnectionsActivity activity, Station station, Departure departure) {
        mActivity = activity;
        mStation = station;
        mDeparture = departure;

        ((TimeForCoffeeApplication) activity.getApplication()).inject(this);
        mEventBus.register(this);
    }

    public void onResumeView() { }

    public void onRefreshView() {
        updateConnections();
    }

    public void onPauseView() { }

    public void onDestroy() {
        mEventBus.unregister(this);
        mActivity = null;
    }

    public Station getStation() {
        return mStation;
    }

    public Departure getDeparture() {
        return mDeparture;
    }

    public boolean getIsFavorite() {
        return mDeparture.getIsFavorite();
    }

    public void updateConnections() {
        if (mConnections == null || mConnections.size() == 0) {
            mActivity.showProgressLayout(true);
        }

        mEventBus.post(new FetchConnectionsEvent(
                mStation.getId(),
                mDeparture.getDestinationId(),
                mDeparture.getDepartureStrForBackend(),
                mDeparture.getArrivalStrForBackend())
        );
    }

    public void toggleFavorite() {
        if (getIsFavorite()) {
            //Remove from fav
            mDeparture.setIsFavorite(false);
            favoritesDataSource.deleteFavoriteLine(mActivity, mDeparture);
        }
        else {
            //Add to fav
            mDeparture.setIsFavorite(true);
            favoritesDataSource.insertFavoriteLine(mActivity, mDeparture);
        }
    }

    @Subscribe
    public void onConnectionsFetchedEvent(ConnectionsFetchedEvent event) {
        mActivity.showProgressLayout(false);

        mConnections = event.getConnections();
        mActivity.updateConnections(mConnections);
    }

    @Subscribe
    public void onFetchErrorEvent(FetchConnectionsErrorEvent event) {
        mActivity.showProgressLayout(false);
        SnackBars.showNetworkError(mActivity, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateConnections();
            }
        });
    }
} public class DeparturesPresenter implements Presenter {

    private DeparturesActivity mActivity;
    private Station mStation;
    private List<Departure> mDepartures;
    private List<Departure> mFavoriteDepartures;
    private Timer mAutoUpdateTimer;

    @Inject
    EventBus mEventBus;

    @Inject
    DepartureService departureService;

    @Inject
    BackendService backendService;

    @Inject
    FavoritesDataSource favoritesDataSource;

    public DeparturesPresenter(DeparturesActivity activity, Station station) {
        mActivity = activity;
        mStation = station;

        ((TimeForCoffeeApplication) activity.getApplication()).inject(this);
        mEventBus.register(this);
    }

    public void onResumeView() { }

    public void onRefreshView() {
        updateDepartures();
    }

    public void onPauseView() {
        if (mAutoUpdateTimer != null) {
            mAutoUpdateTimer.cancel();
            mAutoUpdateTimer = null;
        }
    }

    public void onDestroy() {
        mEventBus.unregister(this);
        mActivity = null;
    }

    @Subscribe
    public void onDeparturesFetchedEvent(DeparturesFetchedEvent event) {
        mActivity.setAreDeparturesLoading(false);

        mDepartures = event.getDepartures();
        mActivity.updateDepartures(mDepartures);

        updateFavorites();
    }

    @Subscribe
    public void onFetchErrorEvent(FetchDeparturesErrorEvent event) {
        mActivity.setAreDeparturesLoading(false);
        SnackBars.showNetworkError(mActivity, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateDepartures();
            }
        });
    }

    public Station getStation() {
        return mStation;
    }

    public boolean getStationIsFavorite() {
        return mStation.getIsFavorite();
    }

    public void updateDepartures() {
        if (mDepartures == null || mDepartures.size() == 0) {
            mActivity.setAreDeparturesLoading(true);
        }

        mEventBus.post(new FetchDeparturesEvent(mStation.getId()));
    }

    public void updateFavorites() {
        if(mDepartures == null || mDepartures.size() == 0) {
            return;
        }

        mFavoriteDepartures = getFavorites();

        mActivity.updateDepartures(mDepartures);
        mActivity.updateFavorites(mFavoriteDepartures);
    }

    public void toggleStationIsFavorite() {
        if (mStation.getIsFavorite()) {
            mStation.setIsFavorite(false);
            favoritesDataSource.deleteFavoriteStation(mActivity, mStation);
        }
        else {
            mStation.setIsFavorite(true);
            favoritesDataSource.insertFavoriteStation(mActivity, mStation);
        }
    }

    public void updateDepartureIsFavorite(Departure departure, boolean isFavorite) {
        String action, action1;

        if (isFavorite) {
            favoritesDataSource.insertFavoriteLine(mActivity, departure);
            action = mActivity.getResources().getString(R.string.departure_action_add);
            action1 = mActivity.getResources().getString(R.string.departure_action_add_2);
        }
        else {
            favoritesDataSource.deleteFavoriteLine(mActivity, departure);
            action = mActivity.getResources().getString(R.string.departure_action_remove);
            action1 = mActivity.getResources().getString(R.string.departure_action_remove_2);
        }

        String message = mActivity.getResources().getString(R.string.departure_fav_message);
        String messageFormatted = String.format(message, departure.getLine(), departure.getDestinationName(), action, action1);
        mActivity.displayToastMessage(messageFormatted);

        updateFavoritesOnFavoriteList();
    }

    private void updateFavoritesOnFavoriteList() {
        mFavoriteDepartures = getFavorites();
        mActivity.updateFavorites(mFavoriteDepartures);
    }

    private List<Departure> getFavorites() {
        List<Departure> favoriteLines = favoritesDataSource.getAllFavoriteLines(mActivity);
        List<Departure> favoriteDepartures = new ArrayList<>();

        for(Departure departure : mDepartures) {
            boolean contains = false;
            for(Departure favorite : favoriteLines) {
                if(favorite.lineEquals(departure)) {
                    favoriteDepartures.add(departure);
                    contains = true;
                }
            }

            departure.setIsFavorite(contains);
        }

        return favoriteDepartures;
    }
} public class MainPresenter implements Presenter, OnLocationUpdatedListener {

    private final int LOCATION_SMALLEST_DISPLACEMENT = 250;
    private final int LOCATION_INTERVAL = 10000;
    private final String LOG_TAG = "timeforcoffee";

    private MainActivity mActivity;
    private List<Station> mStations;
    private List<Station> mFavoriteStations;

    private Location mLastLocation;
    private boolean mIsCapturingLocation;

    private final String locationPermission = "android.permission.ACCESS_FINE_LOCATION";
    private final int PERMISSION_REQUEST_CODE = 0;
    private PermissionsChecker permissionsChecker;

    @Inject
    EventBus mEventBus;

    @Inject
    StationService stationService;

    @Inject
    BackendService backendService;

    @Inject
    FavoritesDataSource favoritesDataSource;

    public MainPresenter(MainActivity activity) {
        mActivity = activity;

        ((TimeForCoffeeApplication) activity.getApplication()).inject(this);
        mEventBus.register(this);

        permissionsChecker = new PermissionsChecker(activity);
    }

    @Override
    public void onResumeView() { }

    @Override
    public void onPauseView() {
        if (mIsCapturingLocation) {
            stopLocation();
        }
    }

    @Override
    public void onRefreshView() {
        loadStationsWithLastPositionForce();
    }

    public void onDestroy() {
        mActivity = null;
        mEventBus.unregister(this);
    }

    public void updateStations() {
        if (!mIsCapturingLocation) {
            startLocation();
        }
    }

    public void updateFavorites() {
        mFavoriteStations = favoritesDataSource.getAllFavoriteStations(mActivity);
        mActivity.updateFavorites(mFavoriteStations);

        if(mStations != null) {
            for(Station station : mStations) {
                station.setIsFavorite(mFavoriteStations.contains(station));
            }

            mActivity.updateStations(mStations);
        }
    }

    public void updateStationIsFavorite(Station station, boolean isFavorite) {
        if (isFavorite) {
            favoritesDataSource.insertFavoriteStation(mActivity, station);
        }
        else {
            favoritesDataSource.deleteFavoriteStation(mActivity, station);
        }

        updateFavoritesOnFavoriteList();
    }

    private void updateFavoritesOnFavoriteList() {
        mFavoriteStations = favoritesDataSource.getAllFavoriteStations(mActivity);
        mActivity.updateFavorites(mFavoriteStations);
    }

    private void startLocation() {
        if (!permissionsChecker.LacksPermission(locationPermission)) {
            if(mStations == null || mStations.size() == 0) {
                mActivity.setIsPositionLoading(true);
            }

            if (!SmartLocation.with(mActivity).location().state().locationServicesEnabled()) {
                SnackBars.showLocalisationServiceOff(mActivity);
                mActivity.setIsPositionLoading(false);
                return;
            }
            else if(SmartLocation.with(mActivity).location().state().isGpsAvailable() && !SmartLocation.with(mActivity).location().state().isNetworkAvailable()) {
                SnackBars.showLocalisationServiceSetToDeviceOnly(mActivity);
                mActivity.setIsPositionLoading(false);
                return;
            }

            mIsCapturingLocation = true;
            LocationParams locationParams = new LocationParams.Builder().setAccuracy(LocationAccuracy.MEDIUM).setDistance(LOCATION_SMALLEST_DISPLACEMENT).setInterval(LOCATION_INTERVAL).build();
            SmartLocation.with(mActivity).location()
                    .config(locationParams)
                    .oneFix()
                    .start(this);
        }
        else {
            permissionsChecker.RequestPermission(mActivity, locationPermission, PERMISSION_REQUEST_CODE, mActivity.getResources().getString(R.string.permission_message));
        }
    }

    private void stopLocation() {
        mIsCapturingLocation = false;
        SmartLocation.with(mActivity).location().stop();
    }

    private void loadStationsWithLastPosition() {
        if(mStations == null || mStations.size() == 0) {
            mActivity.setIsPositionLoading(true);
        }

        if (mLastLocation != null) {
            loadStations(mLastLocation);
        }
        else {
            startLocation();
        }
    }

    private void loadStationsWithLastPositionForce() {
        mActivity.updateStations(new ArrayList<Station>());
        mActivity.setIsPositionLoading(true);

        if (mLastLocation != null) {
            loadStations(mLastLocation);
        }
        else {
            startLocation();
        }
    }

    private void loadStations(Location location) {
        if (location != null) {
            Log.i(LOG_TAG, "get stations for lat =  " + location.getLatitude() + " and long = " + location.getLongitude());
            mEventBus.post(new FetchStationsLocationEvent(location.getLatitude(), location.getLongitude()));
        }
    }

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == PERMISSION_REQUEST_CODE) {
            for (int i = 0; i < permissions.length; i++) {
                String permission = permissions[i];
                int grantResult = grantResults[i];

                if (!(permission.equals(locationPermission))) {
                    continue;
                }

                if (grantResult == PackageManager.PERMISSION_GRANTED) {
                    startLocation();
                } else {
                    mActivity.setIsPositionLoading(false);
                    SnackBars.showLocalisationSettings(mActivity);
                }
            }
        }
    }

    @Override
    public void onLocationUpdated(Location location) {
        Log.i(LOG_TAG, "onLocationUpdated : lat = " + location.getLatitude() + " , long = " + location.getLongitude());
        mLastLocation = location;

        loadStations(location);
    }

    @Subscribe
    public void onStationsFetched(StationsLocationFetchedEvent event) {
        mActivity.setIsPositionLoading(false);

        mStations = event.getStations();
        mActivity.updateStations(mStations);

        updateFavorites();
    }

    @Subscribe
    public void onFetchErrorEvent(FetchStationsLocationErrorEvent event) {
        mActivity.setIsPositionLoading(false);
        SnackBars.showNetworkError(mActivity, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                loadStationsWithLastPosition();
            }
        });
    }
} public class StationSearchPresenter implements Presenter {

    private StationSearchFragment mFragment;
    private List<Station> mStations;
    private String mSearchQuery;

    @Inject
    EventBus mEventBus;

    @Inject
    StationService stationService;

    @Inject
    FavoritesDataSource favoritesDataSource;

    public StationSearchPresenter(StationSearchFragment fragment, String searchQuery) {
        mFragment = fragment;
        mSearchQuery = searchQuery;

        ((TimeForCoffeeApplication) fragment.getActivity().getApplication()).inject(this);
        mEventBus.register(this);
    }

    @Override
    public void onResumeView() { }

    @Override
    public void onRefreshView() {
        search();
    }

    @Override
    public void onPauseView() { }

    @Override
    public void onDestroy() {
        mFragment = null;
        mEventBus.unregister(this);
    }

    @Subscribe
    public void onStationsFetchedEvent(StationsSearchFetchedEvent event) {
        mFragment.setIsSearchLoading(false);
        mStations = event.getStations();

        List<Station> favoriteStations = favoritesDataSource.getAllFavoriteStations(mFragment.getContext());
        for(Station station : mStations) {
            station.setIsFavorite(favoriteStations.contains(station));
        }

        mFragment.updateStations(mStations);
    }

    @Subscribe
    public void onFetchErrorEvent(FetchStationsSearchErrorEvent event) {
        mFragment.setIsSearchLoading(false);
        SnackBars.showNetworkError(mFragment.getActivity(), new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                search();
            }
        });
    }

    public void setSearchQuery(String searchQuery) {
        mSearchQuery = searchQuery;
    }

    public String getSearchQuery() {
        return mSearchQuery;
    }

    public void search() {
        if (mSearchQuery != null && !mSearchQuery.isEmpty()) {
            mFragment.setIsSearchLoading(true);
            mEventBus.post(new FetchStationsSearchEvent(mSearchQuery));
        }
    }

    public void updateStationIsFavorite(Station station, boolean isFavorite) {
        if (isFavorite) {
            favoritesDataSource.insertFavoriteStation(mFragment.getContext(), station);
        }
        else {
            favoritesDataSource.deleteFavoriteStation(mFragment.getContext(), station);
        }
    }

    public void clear() {
        if (mStations != null) {
            mStations.clear();
            mFragment.updateStations(mStations);
        }
    }
}
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.greenrobot.eventbus.EventBus;
import java.util.Date;
import javax.inject.Singleton;
import ch.liip.timeforcoffee.api.ConnectionService;
import ch.liip.timeforcoffee.api.DepartureService;
import ch.liip.timeforcoffee.api.StationService;
import ch.liip.timeforcoffee.api.deserializers.DateDeserializer;
import ch.liip.timeforcoffee.backend.BackendService;
import ch.liip.timeforcoffee.backend.OpenDataService;
import ch.liip.timeforcoffee.fragment.DepartureListFragment;
import ch.liip.timeforcoffee.fragment.FavoritesListFragment;
import ch.liip.timeforcoffee.fragment.StationListFragment;
import ch.liip.timeforcoffee.helper.FavoritesDataSource;
import ch.liip.timeforcoffee.presenter.ConnectionsPresenter;
import ch.liip.timeforcoffee.presenter.DeparturesPresenter;
import ch.liip.timeforcoffee.presenter.MainPresenter;
import ch.liip.timeforcoffee.presenter.StationSearchPresenter;
import dagger.Module;
import dagger.Provides;
import retrofit.RestAdapter;
import retrofit.converter.GsonConverter;
package ch.liip.timeforcoffee;






@Module(
        injects = {
                MainPresenter.class,