package com.pluscubed.plustimer.utils;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import com.pluscubed.plustimer.model.PuzzleType;
import com.pluscubed.plustimer.model.Session;
import com.pluscubed.plustimer.model.Solve;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import rx.Single;
/**
* Utilities class
*/
public class Utils {
private static final Type SESSION_LIST_TYPE;
private static Gson gson;
static {
gson = new GsonBuilder()
.registerTypeAdapter(Session.class, (JsonDeserializer<Session>) (json, typeOfT, context) -> {
Session s = new Gson().fromJson(json, typeOfT);
//TODO: Something something legacy
/*for (final Solve solve : s.mSolves) {
solve.attachSession(s);
}*/
return s;
})
.create();
SESSION_LIST_TYPE = new TypeToken<List<Session>>() {
}.getType();
}
public static int convertDpToPx(Context context, float dp) {
return (int) (dp * context.getResources().getDisplayMetrics().density
+ 0.5f);
}
public static int compare(long lhs, long rhs) {
return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
}
public static void lockOrientation(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
}
Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int rotation = display.getRotation();
int tempOrientation = activity.getResources().getConfiguration().orientation;
int orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
switch (tempOrientation) {
case Configuration.ORIENTATION_LANDSCAPE:
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90)
orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
else
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
break;
case Configuration.ORIENTATION_PORTRAIT:
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270)
orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
else
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
}
activity.setRequestedOrientation(orientation);
}
public static void unlockOrientation(Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
/**
* Save a list of sessions to a file.
*/
@Deprecated
public static void saveSessionListToFile(Context context,
String fileName,
List<Session> sessionList) {
if (sessionList.size() >= 1) {
Writer writer = null;
try {
OutputStream out = context.openFileOutput(fileName,
Context.MODE_PRIVATE);
writer = new OutputStreamWriter(out);
gson.toJson(sessionList, SESSION_LIST_TYPE, writer);
} catch (FileNotFoundException e) {
//File not found: create new file
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* Get list and save new list
*/
@Deprecated
private static void updateData(Context context, String fileName) {
List<Session> historySessions = getSessionListFromFile(context, fileName);
saveSessionListToFile(context, fileName, historySessions);
}
/**
* For updating data w/ old JSON structure
*/
@Deprecated
public static void updateData(Context context, String fileName, Gson oldGson) {
Gson current = gson;
gson = oldGson;
updateData(context, fileName);
gson = current;
}
/**
* Load up the sessions stored in the list. If the file doesn't exist,
* create an empty list.
*/
@Deprecated
public static List<Session> getSessionListFromFile(Context context,
String fileName) {
BufferedReader reader = null;
try {
InputStream in = context.openFileInput(fileName);
reader = new BufferedReader(new InputStreamReader(in));
List<Session> fileSessions = gson.fromJson(reader, SESSION_LIST_TYPE);
if (fileSessions != null) {
return fileSessions;
}
} catch (FileNotFoundException e) {
//File not found: create empty list
} catch (JsonSyntaxException e) {
ErrorUtils.showJsonSyntaxError(context, e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new ArrayList<>();
}
/**
* Returns a String containing the date and time according the device's
* settings and locale from
* the timestamp
*
* @param context the context
* @param timestamp the timestamp to convert into a date & time
* String
* @return the String converted from the timestamp
* @see android.text.format.DateFormat
* @see java.text.DateFormat
*/
public static String dateTimeSecondsStringFromTimestamp(Context context, long timestamp) {
String timeDate;
String androidDateTime = dateTimeStringFromTimestamp(context, timestamp);
String javaDateTime = DateFormat.getDateTimeInstance().format(new
Date(timestamp));
String AmPm = "";
if (!Character.isDigit(androidDateTime.charAt(androidDateTime.length
() - 1))) {
if (androidDateTime.contains(
new SimpleDateFormat().getDateFormatSymbols()
.getAmPmStrings()[Calendar.AM])) {
AmPm = " " + new SimpleDateFormat().getDateFormatSymbols()
.getAmPmStrings()[Calendar.AM];
} else {
AmPm = " " + new SimpleDateFormat().getDateFormatSymbols()
.getAmPmStrings()[Calendar.PM];
}
androidDateTime = androidDateTime.replace(AmPm, "");
}
if (!Character.isDigit(javaDateTime.charAt(javaDateTime.length() - 1)
)) {
javaDateTime = javaDateTime.replace(" " + new SimpleDateFormat()
.getDateFormatSymbols()
.getAmPmStrings()[Calendar.AM], "");
javaDateTime = javaDateTime.replace(" " + new SimpleDateFormat()
.getDateFormatSymbols()
.getAmPmStrings()[Calendar.PM], "");
}
javaDateTime = javaDateTime.substring(javaDateTime.length() - 3);
timeDate = androidDateTime.concat(javaDateTime);
return timeDate.concat(AmPm);
}
@NonNull
public static String dateTimeStringFromTimestamp(Context context, long timestamp) {
return android.text.format.DateFormat.getDateFormat(context).format(new Date(timestamp))
+ " " +
android.text.format.DateFormat.getTimeFormat(context).format(new Date(timestamp));
}
/**
* Returns a String containing hours, minutes,
* and seconds (to the millisecond) from a duration
* in nanoseconds.
*
* @param nanoseconds the duration to be converted
* @return the String converted from the nanoseconds
*/
//TODO: Localization of timeStringFromNs
public static String timeStringFromNs(long nanoseconds,
boolean enableMilliseconds) {
String[] array = timeStringsFromNsSplitByDecimal(nanoseconds,
enableMilliseconds);
return array[0] + "." + array[1];
}
public static String timeStringSecondsFromNs(long nanoseconds,
boolean enableMilliseconds) {
double seconds;
if (enableMilliseconds) {
seconds = Math.floor(nanoseconds / 1000000000.0 * 1000.0) / 1000.0;
} else {
seconds = Math.floor(nanoseconds / 1000000000.0 * 100.0) / 100.0;
}
if (seconds == (long) seconds)
return String.format("%d", (long) seconds);
else
return String.valueOf(seconds);
}
public static String[] timeStringsFromNsSplitByDecimal(long nanoseconds,
boolean enableMilliseconds) {
String[] array = new String[2];
int hours = (int) ((nanoseconds / 1000000000L / 60 / 60) % 24);
int minutes = (int) ((nanoseconds / 1000000000L / 60) % 60);
int seconds = (int) ((nanoseconds / 1000000000L) % 60);
// 0x is saying add zeroes for how many digits
if (hours != 0) {
array[0] = String.format("%d:%02d:%02d", hours, minutes, seconds);
} else if (minutes != 0) {
array[0] = String.format("%d:%02d", minutes, seconds);
} else {
array[0] = String.format("%d", seconds);
}
if (enableMilliseconds) {
array[1] = String.format("%03d",
(int) (((nanoseconds / 1000000.0) % 1000.0)));
} else {
array[1] = String.format("%02d",
(int) ((nanoseconds / 10000000.0) % 100.0));
}
return array;
}
/**
* Gets a list of times (calculated with +2s) from the list of {@code
* Solve}s, excluding DNFs.
* If no times are found, an empty list is returned.
*
* @param list the list of solves to extract times from
* @return the list of nanoseconds of times
*/
private static List<Long> getListTimeTwoNoDnf(List<Solve> list) {
ArrayList<Long> timeTwo = new ArrayList<>();
for (Solve i : list) {
if (!(i.getPenalty() == Solve.PENALTY_DNF)) {
timeTwo.add(i.getTimeTwo());
}
}
return timeTwo;
}
/**
* Gets the best {@code Solve} out of the list (lowest time).
* <p>
* If the list contains no solves, null is returned. If the list contains
* only DNFs, the last DNF solve is returned.
*
* @param list the list of solves, not empty
* @return the solve with the lowest time
*/
public static Solve getBestSolveOfList(List<Solve> list) {
List<Solve> solveList = new ArrayList<>(list);
if (solveList.size() > 0) {
Collections.reverse(solveList);
List<Long> times = getListTimeTwoNoDnf(solveList);
if (times.size() > 0) {
long bestTimeTwo = Collections.min(times);
for (Solve i : solveList) {
if (!(i.getPenalty() == Solve.PENALTY_DNF) && i
.getTimeTwo() == bestTimeTwo) {
return i;
}
}
}
return solveList.get(0);
}
return null;
}
/**
* Gets the worst {@code Solve} out of the list (highest time).
* <p>
* If the list contains DNFs, the last DNF solve is returned.
* If the list contains no solves, null is returned.
*
* @param list the list of solves, not empty
* @return the solve with the highest time
*/
public static Solve getWorstSolveOfList(List<Solve> list) {
List<Solve> solveList = new ArrayList<>(list);
if (solveList.size() > 0) {
Collections.reverse(solveList);
for (Solve i : solveList) {
if (i.getPenalty() == Solve.PENALTY_DNF) {
return i;
}
}
List<Long> times = getListTimeTwoNoDnf(solveList);
if (times.size() > 0) {
long worstTimeTwo = Collections.max(times);
for (Solve i : solveList) {
if (i.getTimeTwo() == worstTimeTwo) {
return i;
}
}
}
}
return null;
}
/**
* Converts a sequence of moves in WCA notation to SiGN notation
*
* @param wca the sequence of moves in WCA notation
* @return the converted sequence of moves in SiGN notation
*/
public static Single<String> wcaToSignNotation(Context context, String wca, String puzzleTypeId) {
return PuzzleType.get(context, puzzleTypeId)
.map(puzzleType -> {
if (Character.isDigit(puzzleType.getScrambler().charAt(0))) {
String[] moves = wca.split(" ");
for (int i = 0; i < moves.length; i++) {
if (moves[i].contains("w")) {
moves[i] = moves[i].replace("w", "");
moves[i] = moves[i].toLowerCase();
}
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < moves.length; i++) {
builder.append(moves[i]);
if (i != moves.length - 1) builder.append(" ");
}
return builder.toString();
} else {
return wca;
}
});
}
/**
* Converts a sequence of moves in SiGN notation to WCA notation
*
* @param sign the sequence of moves in SiGN notation
* @return the converted sequence of moves in WCA notation
*/
public static Single<String> signToWcaNotation(Context context, String sign, String puzzleTypeId) {
return PuzzleType.get(context, puzzleTypeId)
.map(puzzleType -> {
if (Character.isDigit(puzzleType.getScrambler().charAt(0))) {
String[] moves = sign.split(" ");
for (int i = 0; i < moves.length; i++) {
if (!moves[i].equals(moves[i].toUpperCase())) {
char[] possibleMoves = "udfrlb".toCharArray();
for (char move : possibleMoves) {
moves[i] = moves[i].replace(String.valueOf(move),
Character.toUpperCase(move) + "w");
}
}
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < moves.length; i++) {
builder.append(moves[i]);
if (i != moves.length - 1) builder.append(" ");
}
return builder.toString();
} else {
return sign;
}
});
}
/**
* Gets the average of a list of solves, excluding the best and worst
* solves (5%).
* Returns {@link Long#MAX_VALUE} for DNF and {@link
* Session#GET_AVERAGE_INVALID_NOT_ENOUGH} if the list size is less than 3.
*
* @param list the list of solves
* @return the average of the solves
*/
public static long getAverageOf(List<Solve> list) {
if (list.size() >= 3) {
int trim = (int) Math.ceil(list.size() / 20d);
List<Long> times = getListTimeTwoNoDnf(list);
int dnfCount = 0;
for (Solve i : list) {
if (i.getPenalty() == Solve.PENALTY_DNF) dnfCount++;
}
//If the number of DNFs can be cut off by the trim
if (dnfCount <= trim) {
Collections.sort(times);
times = times.subList(trim, times.size() - trim + dnfCount);
long sum = 0;
for (long i : times) {
sum += i;
}
return sum / (times.size());
}
return Long.MAX_VALUE;
}
return Session.GET_AVERAGE_INVALID_NOT_ENOUGH;
}
//Taken from http://stackoverflow.com/questions/19908003
public static int getTextViewHeight(TextView textView) {
WindowManager wm =
(WindowManager) textView.getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int deviceWidth;
Point size = new Point();
display.getSize(size);
deviceWidth = size.x;
int widthMeasureSpec =
View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
textView.measure(widthMeasureSpec, heightMeasureSpec);
return textView.getMeasuredHeight();
}
public static Single<String> getUiScramble(Context context, String scramble,
boolean signEnabled,
String puzzleTypeId) {
return signEnabled ? Utils.wcaToSignNotation(context, scramble, puzzleTypeId) : Single.just(scramble);
}
}