package org.solovyev.android.calculator.memory; import android.os.Handler; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import com.squareup.otto.Bus; import jscl.math.Expression; import jscl.math.Generic; import jscl.math.JsclInteger; import jscl.text.ParseException; import org.solovyev.android.Check; import org.solovyev.android.calculator.*; import org.solovyev.android.io.FileSystem; import javax.annotation.Nonnull; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.io.File; import java.io.IOException; import java.util.concurrent.Executor; @Singleton public class Memory { @NonNull private static final Generic EMPTY = numeric(Expression.valueOf(JsclInteger.ZERO)); @NonNull private final FileSystem fileSystem; @NonNull private final File filesDir; @NonNull private final WriteTask writeTask = new WriteTask(); @NonNull private final Runnables whenLoadedRunnables = new Runnables(); @NonNull private final Handler handler; @Inject Notifier notifier; @Inject ToJsclTextProcessor jsclProcessor; @Named(AppModule.THREAD_BACKGROUND) @Inject Executor backgroundThread; @Inject Bus bus; @NonNull private Generic value = EMPTY; private boolean loaded; @Inject public Memory(@NonNull @Named(AppModule.THREAD_INIT) Executor initThread, @NonNull FileSystem fileSystem, @NonNull @Named(AppModule.DIR_FILES) File filesDir, @NonNull Handler handler) { this.fileSystem = fileSystem; this.filesDir = filesDir; this.handler = handler; initThread.execute(new Runnable() { @Override public void run() { initAsync(); } }); } @NonNull private static Generic numeric(@NonNull Generic generic) { try { return generic.numeric(); } catch (RuntimeException e) { return generic; } } private void initAsync() { Check.isNotMainThread(); final Generic value = loadValue(); handler.post(new Runnable() { @Override public void run() { onLoaded(value); } }); } private void onLoaded(@NonNull Generic value) { this.value = value; this.loaded = true; this.whenLoadedRunnables.run(); } @NonNull private Generic loadValue() { Check.isNotMainThread(); try { final CharSequence value = fileSystem.read(getFile()); return TextUtils.isEmpty(value) ? EMPTY : numeric(Expression.valueOf(value.toString())); } catch (IOException | ParseException e) { Log.e(App.TAG, e.getMessage(), e); } return EMPTY; } public void add(@NonNull final Generic that) { Check.isMainThread(); if (!loaded) { postAdd(that); return; } try { setValue(value.add(that)); } catch (RuntimeException e) { notifier.showMessage(e.getLocalizedMessage()); } } private void postAdd(@NonNull final Generic that) { whenLoadedRunnables.add(new Runnable() { @Override public void run() { add(that); } }); } public void subtract(@NonNull final Generic that) { Check.isMainThread(); if (!loaded) { postSubtract(that); return; } try { setValue(value.subtract(that)); } catch (RuntimeException e) { notifier.showMessage(e.getLocalizedMessage()); } } private void postSubtract(@NonNull final Generic that) { whenLoadedRunnables.add(new Runnable() { @Override public void run() { subtract(that); } }); } @NonNull private String getValue() { Check.isTrue(loaded); try { return value.toString(); } catch (RuntimeException e) { Log.w(App.TAG, e.getMessage(), e); } return ""; } private void setValue(@NonNull Generic newValue) { Check.isTrue(loaded); value = numeric(newValue); handler.removeCallbacks(writeTask); handler.postDelayed(writeTask, 3000L); show(); } private void show() { notifier.showMessage(getValue()); } public void clear() { Check.isMainThread(); if (!loaded) { postClear(); return; } setValue(EMPTY); } private void postClear() { whenLoadedRunnables.add(new Runnable() { @Override public void run() { clear(); } }); } @Nonnull private File getFile() { return new File(filesDir, "memory.txt"); } public void requestValue() { if (!loaded) { postValue(); return; } bus.post(new ValueReadyEvent(getValue())); } private void postValue() { whenLoadedRunnables.add(new Runnable() { @Override public void run() { requestValue(); } }); } public void requestShow() { if (!loaded) { postShow(); return; } show(); } private void postShow() { whenLoadedRunnables.add(new Runnable() { @Override public void run() { requestShow(); } }); } public static final class ValueReadyEvent { @NonNull public final String value; public ValueReadyEvent(@NonNull String value) { this.value = value; } } private class WriteTask implements Runnable { @Override public void run() { Check.isMainThread(); if (!loaded) { return; } final String value = getValue(); backgroundThread.execute(new Runnable() { @Override public void run() { fileSystem.writeSilently(getFile(), prepareExpression(value)); } }); } @NonNull private String prepareExpression(@NonNull String value) { try { return jsclProcessor.process(value).getValue(); } catch (org.solovyev.android.calculator.ParseException ignored) { return value; } } } }