/*
 * Copyright (c) 2015 Diona Ltd.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of Diona
 * ("Confidential Information"). You shall not disclose such Confidential Information
 * and shall use it only in accordance with the terms of the license agreement you
 * entered into with Diona.
 */
package org.odk.collect.android.views;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

import org.odk.collect.android.application.Collect;
import org.odk.collect.android.utilities.FileUtils;

import java.io.File;

/**
 * A View that the user can draw on.
 */
public class DrawView extends View {

  private static final int BACKGROUND_COLOR = Color.WHITE;
  private static final double SIGNATURE_HEIGHT_OFFSET = .7;
  private boolean isSignature;
  private Bitmap mBitmap;
  private Canvas mCanvas;
  private Path mCurrentPath;
  private Paint mBitmapPaint;
  private Paint paint;
  private Paint pointPaint;
  private File mBackgroundBitmapFile;
  private boolean changed;
  private Callback callback;

  /**
   * @param c
   *     the Context in which the View is inflated.
   */
  public DrawView(final Context c) {
    super(c);
    isSignature = false;
    mBitmapPaint = new Paint(Paint.DITHER_FLAG);
    mCurrentPath = new Path();
    setBackgroundColor(BACKGROUND_COLOR);
    mBackgroundBitmapFile = new File(Collect.TMPDRAWFILE_PATH);
  }

  /**
   * @param c
   *     the Context in which the View is inflated.
   * @param isSignature
   *     {@code true} if this is a signature, {@code false} if it is another type of drawing.
   * @param f
   *     the file used to hold the file.
   */
  public DrawView(Context c, boolean isSignature, File f) {
    this(c);
    this.isSignature = isSignature;
    mBackgroundBitmapFile = f;
  }

  /**
   * Undo all changes made to the View since it was last saved.
   */
  public void reset() {
    final WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
    final Display display = wm.getDefaultDisplay();
    final Point size = new Point();
    display.getSize(size);
    int screenWidth = size.x;
    int screenHeight = size.y;
    resetImage(screenWidth, screenHeight);
    changed = false;
    updateCallback();
  }

  private void updateCallback() {
    if (callback != null) {
      callback.setChanged(changed);
    }
  }

  /**
   * Undo all changes made to the View since it was last saved.
   *
   * @param h
   *     the height of the reset image.
   * @param w
   *     the width of the reset image.
   */
  public void resetImage(int w, int h) {
    final boolean isNew = !mBackgroundBitmapFile.exists();
    if (isNew) {
      mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
      mCanvas = new Canvas(mBitmap);
      mCanvas.drawColor(BACKGROUND_COLOR);
      if (isSignature) {
        drawSignLine();
      }
    } else {
      // Because this activity is used in a fixed landscape mode only, sometimes resetImage()
      // is called upon with flipped w/h (before orientation changes have been applied)
      if (w > h) {
        final int temp = w;
        w = h;
        h = temp;
      }
      mBitmap =
          FileUtils.getBitmapAccuratelyScaledToDisplay(mBackgroundBitmapFile, w, h).copy(Bitmap.Config.ARGB_8888, true);
      mCanvas = new Canvas(mBitmap);
    }
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    resetImage(w, h);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    canvas.drawColor(BACKGROUND_COLOR);
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
    canvas.drawPath(mCurrentPath, paint);
  }

  private float mX, mY;

  private void touch_start(float x, float y) {
    mCurrentPath.reset();
    mCurrentPath.moveTo(x, y);
    mX = x;
    mY = y;
  }

  /**
   * Draw a signature line across the View.
   */
  public void drawSignLine() {
    mCanvas.drawLine(0, (int) (mCanvas.getHeight() * SIGNATURE_HEIGHT_OFFSET),
        mCanvas.getWidth(), (int) (mCanvas.getHeight() * SIGNATURE_HEIGHT_OFFSET), paint);
  }

  private void touch_move(float x, float y) {
    mCurrentPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
    mX = x;
    mY = y;
  }

  private void touch_up() {
    if (mCurrentPath.isEmpty()) {
      mCanvas.drawPoint(mX, mY, pointPaint);
    } else {
      mCurrentPath.lineTo(mX, mY);
      // commit the path to our offscreen
      mCanvas.drawPath(mCurrentPath, paint);
    }
    // kill this so we don't double draw
    mCurrentPath.reset();
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    changed = true;
    updateCallback();
    float x = event.getX();
    float y = event.getY();

    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        touch_start(x, y);
        invalidate();
        break;
      case MotionEvent.ACTION_MOVE:
        touch_move(x, y);
        invalidate();
        break;
      case MotionEvent.ACTION_UP:
        touch_up();
        invalidate();
        break;
    }
    return true;
  }

  /**
   * @param paint
   *     the Paint to use for the drawing.
   */
  public void setPaint(final Paint paint) {
    this.paint = paint;
  }

  /**
   * @param pointPaint
   *     the Paint to be used for points.
   */
  public void setPointPaint(final Paint pointPaint) {
    this.pointPaint = pointPaint;
  }

  /**
   * @return {@code true} if the user has changed the image, {@code false} if the user has not.
   */
  public boolean isChanged() {
    return changed;
  }

  /**
   * @param callback
   *     the callback used for relaying changes in this View.
   */
  public void setCallback(final Callback callback) {
    this.callback = callback;
  }

  /**
   * A callback for changes in this View.
   */
  public interface Callback {

    /**
     * @param changed
     *     {@code true} if the user has changed the image, {@code false} if they have not or their changes have been
     *     reverted.
     */
    void setChanged(final boolean changed);
  }
}
