package com.llamacorp.equate.unit;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Abstract class, note that child class must implement a function to do raw
* number conversion
*/
public class UnitType {
private static final String JSON_NAME = "name";
private static final String JSON_UNIT_DISP_ORDER = "unit_disp_order";
private static final String JSON_CURR_UNIT_POS_IN_ARRAY = "pos";
private static final String JSON_IS_SELECTED = "selected";
private static final String JSON_UNIT_ARRAY = "unit_array";
private static final String JSON_UPDATE_TIME = "update_time";
private String mName;
private ArrayList<Unit> mUnitArray;
private Unit mPrevUnit;
private Unit mCurrUnit;
private boolean mIsUnitSelected;
private boolean mContainsDynamicUnits = false;
//Order to display units (based on mUnitArray index
private ArrayList<Integer> mUnitDisplayOrder;
private String mXMLCurrencyURL;
private boolean mUpdating = false;
private Date mLastUpdateTime;
//this is for communication with fragment hosting convert keys
OnConvertKeyUpdateFinishedListener mCallback;
public interface OnConvertKeyUpdateFinishedListener {
void refreshAllButtonsText();
}
/**
* Default constructor used by UnitType Initializer
*/
public UnitType(String name) {
mName = name;
mUnitArray = new ArrayList<>();
mUnitDisplayOrder = new ArrayList<>();
mIsUnitSelected = false;
mUpdating = false;
mLastUpdateTime = new GregorianCalendar(2015, 3, 1, 1, 11).getTime();
}
public UnitType(String name, String URL) {
this(name);
mXMLCurrencyURL = URL;
}
/**
* Takes JSON object and loads out user saved info, such as currently
* selected unit and unit display order
*
* @param json is the JSON object that contains save data of UnitType
* to load.
*/
public void loadJSON(JSONObject json) throws JSONException {
//Check to make we have the right saved JSON, else leave
if (!getUnitTypeName().equals(json.getString(JSON_NAME)))
return;
//load in saved data from Units (currency values and update times)
//note that no other type of unit is loaded
if (containsDynamicUnits()){
JSONArray jUnitArray = json.getJSONArray(JSON_UNIT_ARRAY);
for (int i = 0; i < jUnitArray.length(); i++) {
getUnit(i).loadJSON(jUnitArray.getJSONObject(i));
}
}
mCurrUnit = getUnitPosInUnitArray(json.getInt(JSON_CURR_UNIT_POS_IN_ARRAY));
mIsUnitSelected = json.getBoolean(JSON_IS_SELECTED);
setLastUpdateTime(new Date(json.getLong(JSON_UPDATE_TIME)));
JSONArray jUnitDisOrder = json.getJSONArray(JSON_UNIT_DISP_ORDER);
mUnitDisplayOrder.clear();
for (int i = 0; i < jUnitDisOrder.length(); i++) {
mUnitDisplayOrder.add(jUnitDisOrder.getInt(i));
}
//fill in the remaining if missing (if we added a unit)
fillUnitDisplayOrder();
}
/**
* Save the state of this UnitType into a JSON object for later use
*
* @return JSON object that contains this object
*/
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
//should only be saving data from Currency unit type
if (containsDynamicUnits()){
JSONArray jUnitArray = new JSONArray();
for (Unit unit : mUnitArray)
jUnitArray.put(unit.toJSON());
json.put(JSON_UNIT_ARRAY, jUnitArray);
}
//used to identify this UnitType from others
json.put(JSON_NAME, mName);
json.put(JSON_CURR_UNIT_POS_IN_ARRAY, findUnitPosInUnitArray(mCurrUnit));
json.put(JSON_IS_SELECTED, mIsUnitSelected);
json.put(JSON_UPDATE_TIME, getLastUpdateTime().getTime());
JSONArray jUnitDisOrder = new JSONArray();
for (Integer i : mUnitDisplayOrder)
jUnitDisOrder.put(i);
json.put(JSON_UNIT_DISP_ORDER, jUnitDisOrder);
return json;
}
/**
* Used to build a UnitType after it has been created
*/
public void addUnit(Unit u) {
mUnitArray.add(u);
//0th element is 0, 1st is 1, etc
mUnitDisplayOrder.add(mUnitDisplayOrder.size());
//if there was already a dynamic unit or this one is, UnitType still contains dynamic units
if (mContainsDynamicUnits || u.isDynamic()) mContainsDynamicUnits = true;
}
/**
* Swap positions of units
*/
public void swapUnits(int pos1, int pos2) {
Collections.swap(mUnitDisplayOrder, pos1, pos2);
}
/**
* Rotates the units in the display order such that the unit at toIndex - 1
* now has position fromIndex, the unit at fromIndex has position fromIndex +
* 1, etc.
*
* @param fromIndex is inclusive
* @param toIndex is exclusive
*/
public void rotateUnitSublist(int fromIndex, int toIndex) {
Collections.rotate(mUnitDisplayOrder.subList(fromIndex, toIndex), 1);
}
/**
* Sorts a sublist of units in display order by name
*
* @param fromIndex is inclusive
* @param toIndex is exclusive
*/
public void sortUnitSublist(int fromIndex, int toIndex) {
Collections.sort(mUnitDisplayOrder.subList(fromIndex, toIndex),
new Comparator<Integer>() {
public int compare(Integer s1, Integer s2) {
return mUnitArray.get(s1).getLongName()
.compareTo(mUnitArray.get(s2).getLongName());
}
});
}
/**
* Find the position of the unit in the current unit button order
*
* @return -1 if selection failed, otherwise the position of the unit
*/
public int findUnitButtonPosition(Unit unit) {
if (unit == null)
return -1;
for (int i = 0; i < size(); i++) {
if (unit.equals(getUnit(i)))
return i; //found the unit
}
return -1; //if we didn't find the unit
}
public int findUnitPosInUnitArray(Unit unit) {
if (unit == null) return -1;
for (int i = 0; i < size(); i++) {
if (unit.equals(getUnitPosInUnitArray(i)))
return i; //found the unit
}
return -1; //if we didn't find the unit
}
public int findButtonPositionforUnitArrayPos(int pos) {
return mUnitDisplayOrder.indexOf(pos);
}
/**
* If mCurrUnit not set, set mCurrUnit
* If mCurrUnit already set, call functions to perform a convert
*
* @return returns if a conversion is requested
*/
public boolean selectUnit(int clickedButPos) {
Unit unitPressed = getUnit(clickedButPos);
//used to tell caller if we needed to do a conversion
boolean requestConvert = false;
//If we've already selected a unit, do conversion
if (mIsUnitSelected){
//if the unit is the same as before, de-select it
if (getCurrUnitButtonPos() == clickedButPos){
//if historical unit, allow selection again (for a different year)
if (!unitPressed.isHistorical()){
mIsUnitSelected = false;
return false;
}
}
mPrevUnit = mCurrUnit;
requestConvert = true;
}
//Select new unit regardless
mCurrUnit = unitPressed;
//Engage set flag
mIsUnitSelected = true;
return requestConvert;
}
/**
* Update values of units that are not static (currency) via
* each unit's own HTTP/JSON api call. Note that this refresh
* is asynchronous and will only happen sometime in the future
* Internet connection permitting.
*/
public void refreshDynamicUnits(Context c, boolean forced) {
if (containsDynamicUnits()){
UnitTypeUpdater utp = new UnitTypeUpdater(c);
utp.update(this, forced);
}
}
/**
* Check to see if this UnitType holds any units that have values that
* need to be refreshed via the Internet
*/
public boolean containsDynamicUnits() {
return mContainsDynamicUnits;
}
// /** Check to see if unit at position pos is currently updating */
// public boolean isUnitUpdating(int pos){
// //TODO have this return true if isUpdating is true, otherwise do the following
// //TODO also make conv keys reflect this change (so all will show updating)
// //TODO but once yahoo xml update is finished, individuals will show updating
// if(getUnit(pos).isDynamic())
// return ((UnitCurrency)getUnit(pos)).isUpdating();
// else
// return false;
// }
/**
* Check to see if unit at position pos is dynamic
*/
public boolean isUnitDynamic(int pos) {
return getUnit(pos).isDynamic();
}
public void setDynamicUnitCallback(OnConvertKeyUpdateFinishedListener callback) {
if (containsDynamicUnits()){
mCallback = callback;
// for (int i = 0; i < size(); i++)
// if (getUnit(i).isDynamic())
// ((UnitCurrency) getUnit(i)).setCallback(mCallback);
}
}
/**
* Check to see if unit at position pos is dynamic
*/
public boolean isUnitHistorical(int pos) {
return getUnit(pos).isHistorical();
}
/**
* Resets mIsUnitSelected flag
*/
public void clearUnitSelection() {
mIsUnitSelected = false;
}
public boolean isUnitSelected() {
return mIsUnitSelected;
}
public String getUnitTypeName() {
return mName;
}
/**
* @param pos is index of Unit in the mUnitArray list
* @return String name to be displayed on convert button
*/
public String getUnitDisplayName(int pos) {
return getUnit(pos).toString();
}
public String getLowercaseGenericLongName(int pos) {
return getUnit(pos).getLowercaseGenericLongName();
}
/**
* Method builds charSequence array of long names of undisplayed units
*
* @param numDispUnits is Array of long names of units not being displayed
* @return Number of units being displayed, used to find undisplayed units
*/
public CharSequence[] getUndisplayedUnitNames(int numDispUnits) {
//ArrayList<Unit> subList = mUnitArray.subList(numDispUnits, size());
//return subList.toArray(new CharSequence[subLists.size()]);
int arraySize = size() - numDispUnits;
CharSequence[] cs = new CharSequence[arraySize];
for (int i = 0; i < arraySize; i++) {
cs[i] = getUnit(numDispUnits + i).getLongName();
}
return cs;
}
/**
* Used to get the Unit at a given position. Note that the position is the
* user defined order for buttons. Uses a LUT to convert displayed position
* into real array position.
*
* @param buttonPos Position of unit to retrieve (user defined order)
* @return Unit at the given position
*/
public Unit getUnit(int buttonPos) {
//If a unit is added
if (mUnitDisplayOrder.size() < size())
fillUnitDisplayOrder();
//if somehow there are more UnitDisplayOrder (unit deleted), don't want
//to address nonexistent element
if (mUnitDisplayOrder.size() > size())
resetUnitDipplayOrder();
return mUnitArray.get(mUnitDisplayOrder.get(buttonPos));
}
/**
* Get the unit given a position of the original mUnitArray. This does not
* use the mUnitDisplayOrder like getUnit uses. Will return null if pos is
* invalid (less than 0 or greater than mUnitArray size)
*
* @param pos position of unit in mUnitArray
* @return unit from mUnitArray
*/
public Unit getUnitPosInUnitArray(int pos) {
if (pos < 0 || pos >= mUnitArray.size()) return null;
return mUnitArray.get(pos);
}
/**
* Populate the UnitDisplayOrder array. Will fill if empty or top off if
* it has less elements than UnitArray
*/
private void fillUnitDisplayOrder() {
for (int i = mUnitDisplayOrder.size(); i < size(); i++)
mUnitDisplayOrder.add(mUnitDisplayOrder.size());
}
private void resetUnitDipplayOrder() {
mUnitDisplayOrder.clear();
fillUnitDisplayOrder();
}
public String getXMLCurrencyURL() {
return mXMLCurrencyURL;
}
public Unit getPrevUnit() {
return mPrevUnit;
}
public Unit getCurrUnit() {
return mCurrUnit;
}
/**
* Get the number of Units in this UnitType
*
* @return integer of number of Units in this UnitType
*/
public int size() {
return mUnitArray.size();
}
public int getCurrUnitButtonPos() {
return findUnitButtonPosition(mCurrUnit);
}
public void setUpdating(boolean updating) {
mUpdating = updating;
//refresh text
if (mCallback != null)
mCallback.refreshAllButtonsText();
}
public boolean isUpdating() {
return mUpdating;
}
public Date getLastUpdateTime() {
return mLastUpdateTime;
}
public void setLastUpdateTime(Date mLastUpdateTime) {
this.mLastUpdateTime = mLastUpdateTime;
}
}