/FileExtraction/Java_unseen/Kegbot_kegbot-android/kegtab/src/main/java/org/kegbot/app/CalibrationActivity.java
/*
 * Copyright 2003-2020 The Kegbot Project contributors <info@kegbot.org>
 *
 * This file is part of the Kegtab package from the Kegbot project. For
 * more information on Kegtab or Kegbot, see <http://kegbot.org/>.
 *
 * Kegtab is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 2.
 *
 * Kegtab is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Kegtab. If not, see <http://www.gnu.org/licenses/>.
 */
package org.kegbot.app;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;

import com.squareup.otto.Subscribe;

import org.kegbot.app.config.AppConfiguration;
import org.kegbot.app.util.Units;
import org.kegbot.app.view.BadgeView;
import org.kegbot.backend.Backend;
import org.kegbot.backend.BackendException;
import org.kegbot.core.FlowMeter;
import org.kegbot.core.KegbotCore;
import org.kegbot.core.hardware.HardwareManager;
import org.kegbot.core.hardware.MeterUpdateEvent;
import org.kegbot.proto.Models.KegTap;

public class CalibrationActivity extends CoreActivity {

  private static final String TAG = CalibrationActivity.class.getSimpleName();
  private static final boolean DEBUG = false;

  private static final String EXTRA_METER_NAME = "meter_name";
  private static final String EXTRA_ML_PER_TICK = "ml_per_tick";

  // 50% - 150%
  // (0, 0.5)
  // (100, 1.5)
  private static final double SCALE_M = (1.5 - 0.5) / 100;
  // y = Mx + b
  // b = y - Mx
  private static final double SCALE_B = 1.5 - (SCALE_M * 100);

  private HardwareManager mHardwareManager;

  private KegTap mTap;
  private String mMeterName;

  private double mTicksPerMl;
  private double mExistingTicksPerMl;

  private BadgeView mTicksBadge;
  private SeekBar mSeekBar;
  private BadgeView mVolumeBadge;
  private TextView mPercentText;
  private TextView mCalibratedMlTickText;
  private TextView mOriginalMlTickText;

  private Button mDoneButton;
  private Button mResetButton;

  private long mLastReading = Long.MIN_VALUE;
  private long mTicks = 0;

  private boolean mMetric = false;

  @Subscribe
  public void onMeterUpdate(MeterUpdateEvent event) {
    final FlowMeter meter = event.getMeter();
    final String name = meter.getMeterName();
    Log.d(TAG, "Meter update: " + name);
    if (name.equals(mMeterName)) {
      handleMeterUpdate(meter.getTicks());
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.calibration_activity);
  }

  @Override
  protected void onStart() {
    super.onStart();

    final AppConfiguration config = KegbotApplication.get(this).getConfig();
    mMetric = config.getUseMetric();

    mMeterName = getIntent().getStringExtra(EXTRA_METER_NAME);
    mTap = KegbotCore.getInstance(this).getTapManager().getTapForMeterName(mMeterName);
    if (mTap == null || mTap.getMeter() == null) {
      Log.e(TAG, "No tap for meter name: " + mMeterName);
      finish();
      return;
    }

    mExistingTicksPerMl = mTap.getMeter().getTicksPerMl();
    mTicksPerMl = mExistingTicksPerMl;

    mTicksBadge = (BadgeView) findViewById(R.id.ticksBadge);
    mTicksBadge.setBadgeCaption("Ticks");
    mTicksBadge.setBadgeValue("0");

    mVolumeBadge = (BadgeView) findViewById(R.id.volumeBadge);
    if (mMetric) {
      mVolumeBadge.setBadgeCaption(getString(R.string.calibration_badge_actual_metric));
      mVolumeBadge.setBadgeValue("0");
    } else {
      mVolumeBadge.setBadgeCaption(getString(R.string.calibration_badge_actual_imperial));
      mVolumeBadge.setBadgeValue("0.0");
    }

    mVolumeBadge.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        onVolumeBadgeClicked();
      }
    });

    mPercentText = (TextView) findViewById(R.id.calibratePercent);
    mCalibratedMlTickText = (TextView) findViewById(R.id.calibratedTicksPerMl);
    mOriginalMlTickText = (TextView) findViewById(R.id.originalTicksPerMl);
    mOriginalMlTickText.setText(Double.valueOf(mExistingTicksPerMl).toString());
    mOriginalMlTickText.setText(String.format("%.2f", Double.valueOf(mExistingTicksPerMl)));

    mSeekBar = (SeekBar) findViewById(R.id.calibrateSeekBar);
    mSeekBar.setMax(99);
    mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
      }

      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {
      }

      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        double mult = getMultiplier();
        mTicksPerMl = mExistingTicksPerMl * mult;
        updateMetrics();
        if (DEBUG) Log.d(TAG, "onProgressChanged: progress=" + progress + " mult=" + mult);
      }
    });

    mResetButton = (Button) findViewById(R.id.calibrateReset);
    mResetButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        resetCalibration();
      }
    });

    mDoneButton = (Button) findViewById(R.id.calibrateDone);
    mDoneButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finishCalibration();
      }
    });

    Log.d(TAG, "Started: meterName=" + mMeterName);
    resetCalibration();
  }

  @Override
  protected void onResume() {
    super.onResume();
    Log.d(TAG, "onResume");
    mHardwareManager = KegbotCore.getInstance(getApplicationContext()).getHardwareManager();
    mHardwareManager.toggleOutput(mTap, true);

    final KegbotCore core = KegbotCore.getInstance(this);
    core.getBus().register(this);
    core.getFlowManager().setPaused(true);

    mTap = KegbotCore.getInstance(getApplicationContext()).getTapManager().getTapForMeterName(
        mMeterName);
    mHardwareManager.toggleOutput(mTap, true);
  }

  @Override
  protected void onPause() {
    mHardwareManager.toggleOutput(mTap, false);
    final KegbotCore core = KegbotCore.getInstance(this);
    core.getFlowManager().setPaused(false);
    core.getBus().unregister(this);
    super.onPause();
  }

  private void handleMeterUpdate(long meterReading) {
    if (mLastReading < 0) {
      mLastReading = meterReading;
      return;
    }
    final long delta = meterReading - mLastReading;
    mTicks += delta;
    Log.d(TAG, "Update: delta=" + delta + " ticks=" + mTicks);
    mLastReading = meterReading;

    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        updateMetrics();
      }
    });
  }

  private double getMultiplier() {
    double result = SCALE_M * mSeekBar.getProgress() + SCALE_B;
    if (DEBUG) {
      Log.d(TAG, "getMultipler: " + result + " = " + SCALE_M + "*" + mSeekBar.getProgress() + " + "
          + SCALE_B);
    }
    return result;
  }

  private void updateMetrics() {
    mTicksBadge.setBadgeValue(String.valueOf(mTicks));
    mVolumeBadge.setBadgeValue(getDisplayVolume());
    mCalibratedMlTickText.setText(String.format("%.2f", Double.valueOf(mTicksPerMl)));

    Integer percent = Integer.valueOf((int) (getMultiplier() * 100));
    mPercentText.setText(String.format("%s%%", percent));

    if (mTicksPerMl != mExistingTicksPerMl) {
      mResetButton.setEnabled(true);
      mDoneButton.setEnabled(true);
    } else {
      mResetButton.setEnabled(false);
      mDoneButton.setEnabled(false);
    }
  }

  private void resetCalibration() {
    mTicksPerMl = mExistingTicksPerMl;
    mSeekBar.setEnabled(true);
    mSeekBar.setProgress(50);
    mResetButton.setEnabled(false);
    updateMetrics();
  }

  private void finishCalibration() {
    if (mExistingTicksPerMl == mTicksPerMl) {
      Log.d(TAG, "No change.");
      finish();
      return;
    }

    final ProgressDialog dialog = new ProgressDialog(this);
    dialog.setIndeterminate(true);
    dialog.setCancelable(false);
    dialog.setTitle("Calibrating Tap");
    dialog.setMessage("Please wait ...");
    dialog.show();

    new AsyncTask<Void, Void, String>() {
      @Override
      protected String doInBackground(Void... params) {
        final KegbotCore core = KegbotCore.getInstance(CalibrationActivity.this);
        final Backend backend = core.getBackend();

        try {
          backend.calibrateMeter(mTap.getMeter(), mTicksPerMl);
          core.getSyncManager().requestSync();
          return "";
        } catch (BackendException e) {
          Log.w(TAG, "Error calibrating: " + e, e);
          return e.toString();
        }
      }

      @Override
      protected void onPostExecute(String result) {
        dialog.dismiss();
        if (result.isEmpty()) {
          Log.d(TAG, "Calibrated successfully!");
          finish();
          return;
        }
        new AlertDialog.Builder(CalibrationActivity.this)
            .setCancelable(true)
            .setNegativeButton("Ok", null)
            .setTitle("Calibration failed")
            .setMessage("Calibration failed: " + result)
            .show();
      }
    }.execute();

  }

  private void onVolumeBadgeClicked() {
    if (mTicks <= 0) {
      showNeedTicksDialog();
    } else {
      showCustomVolumeDialog();
    }
  }

  /** Admonishes the user to pour something before calibrating. */
  private void showNeedTicksDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setCancelable(true)
        .setTitle(R.string.calibrate_dialog_no_ticks_title)
        .setMessage(R.string.calibrate_dialog_no_ticks_message)
        .setPositiveButton("Ok", null)
        .show();
  }

  /** Prompts the user to enter a custom volume for current ticks. */
  private void showCustomVolumeDialog() {
    final View dialogView =
        getLayoutInflater().inflate(R.layout.calibrate_custom_volume_dialog, null);
    final EditText dialogVolume = (EditText) dialogView.findViewById(R.id.calibrateCustomVolume);
    dialogVolume.append(getDisplayVolume());  // advances cursor to end, too

    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setCancelable(true)
        .setTitle(R.string.calibrate_dialog_custom_title)
        .setMessage(R.string.calibrate_dialog_custom_message)
        .setPositiveButton("Use This Volume", new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int which) {
            Log.d(TAG, "Custom volume: " + dialogVolume.getText());
            onCustomVolumeEntered(dialogVolume.getText().toString());
          }
        })
        .setView(dialogView)
        .setNegativeButton("Cancel", null)
        .show();
  }

  private void onCustomVolumeEntered(String stringValue) {
    final Double value;
    try {
      value = Double.valueOf(stringValue);
    } catch (NumberFormatException e) {
      Log.w(TAG, "Dropping bogus value: " + e);
      return;
    }

    if (value.doubleValue() <= 0) {
      Log.w(TAG, "Value out of range: " + value);
      return;
    }

    if (mMetric) {
      mTicksPerMl = mTicks / value.doubleValue();
    } else {
      mTicksPerMl = mTicks / Units.volumeOuncesToMl(value.doubleValue());
    }

    mVolumeBadge.setBadgeValue(String.format("%.2f", value));
    mSeekBar.setEnabled(false);
    mResetButton.setEnabled(true);
    updateMetrics();
  }

  private double getTicksPerMl() {
    return getMultiplier() * mTicksPerMl;
  }

  private double getVolumeMl() {
    return mTicks / getTicksPerMl();
  }

  private String getDisplayVolume() {
    if (mMetric) {
      return String.format("%s", (int) getVolumeMl());
    } else {
      return String.format("%.2f", Double.valueOf(Units.volumeMlToOunces(getVolumeMl())));
    }
  }

  static Intent getStartIntent(final Context context, final KegTap tap) {
    final Intent intent = new Intent(context, CalibrationActivity.class);
    intent.putExtra(EXTRA_METER_NAME, tap.getMeter().getName());
    intent.putExtra(EXTRA_ML_PER_TICK, 1 / tap.getMeter().getTicksPerMl());
    intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
    return intent;
  }

}
