/****************************************************************************
*
* CanvasView - class extending View class to handle all drawing with canvas
*
* Programmer: Annicka Rabida, May 2021
* 
***************************************************************************/
package com.example.paintingphotos;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;

public class CanvasView extends View {

    private Bitmap mBitmap;
    private Canvas mCanvas;
    private Path mPath;
    private Paint mPaint;
    private int mPaintColor = Color.BLACK;
    private float mPaintHardness = 1;

    private float mColorHSV[] = new float[3]; //0:H 1:S 2:V
    private final int HUE = 0;
    private final int SATURATION = 1;
    private final int VALUE = 2;
    private final int GREYSCALE = 156;
    private static boolean colorModified = false;
    private static boolean eyeDropperActive = false;
    private static boolean disableColorWheel = true;
    private static String selectedBrush;

    private ArrayList<Path> mPathList = new ArrayList<Path>();
    private ArrayList<Paint> mPaintList = new ArrayList<>();
    private int indexPathPaintList = 0;

    private ArrayList<Path> mPastPathList = new ArrayList<>();
    private ArrayList<Paint> mPastPaintList = new ArrayList<>();
    private int indexPastPathPaintList = 0;

    private float mX, mY;
    private static final float TOLERANCE = 5;

    Context context;
    Resources resources;
    private int WIDTH;
    private int HEIGHT;

    public CanvasView(Context c, AttributeSet attrs) {
        super(c, attrs);
        context = c;
        resources = getResources();

        mPath = new Path();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(mPaintColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setBlendMode(BlendMode.SRC_ATOP);
        mPaint.setStrokeWidth(4f);
        mPaint.setMaskFilter(new BlurMaskFilter(mPaintHardness, BlurMaskFilter.Blur.NORMAL));
        Bitmap selectedBitmap = MainActivity.selectedBitmap;
        //Log.w("canvas MSG", "global bitmap: "+ selectedBitmap);
        Bitmap mutableBitmap=selectedBitmap.copy(Bitmap.Config.ARGB_8888, true);
        mCanvas = new Canvas();
        mCanvas.setBitmap(mutableBitmap);
        selectedBrush = "Brush: " + resources.getString(R.string.brush_no_effect);
        this.setDrawingCacheEnabled(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        WIDTH = w;
        HEIGHT = h;
        /** draw onto the defined Bitmap **/
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /** draw the mPath with the mPaint on the canvas when onDraw **/
        for (int i = 0; i < mPathList.size(); i++)
            canvas.drawPath(mPathList.get(i), mPaintList.get(i));
        canvas.drawPath(mPath, mPaint);
    }

    /********************************************************************
     *
     * ACCESSORS
     *
     ******************************************************************/
    public int getColor(){
        return mPaint.getColor();
    }
    public float getBrightness(){
        return mColorHSV[VALUE];
    }
    public float getHSV(int i){
        return mColorHSV[i];
    }
    public float getStrokeSize(){
        return mPaint.getStrokeWidth();
    }
    public float getBrushHardness(){
        return mPaintHardness;
    }
    public boolean disableColorsForBrush(){
        return disableColorWheel;
    }
    public String getBrushType(){
        return selectedBrush;
    }

    /********************************************************************
     *
     * MUTATORS
     *
     ******************************************************************/
    public void setColor(float[] changingHSV){
        mColorHSV[VALUE] = changingHSV[VALUE];
        mColorHSV[HUE] = changingHSV[HUE];
        mColorHSV[SATURATION] = changingHSV[SATURATION];
        mPaintColor = Color.HSVToColor(mColorHSV);
        mPaint.setColor(mPaintColor);
        invalidate();
    }

    public void setBrightness(float v){
        mColorHSV[VALUE] = v;
        mPaintColor = Color.HSVToColor(mColorHSV);
    }

    public void setBrush(String item){
        mBitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
        Log.w("MSG view", "layered bitmap: " + mBitmap);
        mCanvas = new Canvas(mBitmap);

        if (colorModified){ /**Was previous brush used that changed the color from the one the user picked?**/
            mPaint.setColor(mPaintColor);
            colorModified = false;
        }

        if (item == resources.getString(R.string.brush_multiply)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.MULTIPLY);
            disableColorWheel = false;
        } else if (item == resources.getString(R.string.brush_no_effect)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.SRC_ATOP);
            disableColorWheel = false;
        } else if (item == resources.getString(R.string.brush_screen)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.SCREEN);
            disableColorWheel = false;
        } else if (item == resources.getString(R.string.brush_hue)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.HUE);
            disableColorWheel = false;
        } else if (item == resources.getString(R.string.brush_color)) {
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.COLOR);
            mPaint.setColor(Color.rgb(GREYSCALE,GREYSCALE,GREYSCALE));
            colorModified = true;
            disableColorWheel = true;
        } else if(item == resources.getString(R.string.brush_color_dodge)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.COLOR_DODGE);
            mPaint.setColor(Color.rgb(GREYSCALE,GREYSCALE,GREYSCALE));
            colorModified = true;
            disableColorWheel = true;
        } else if (item == resources.getString(R.string.brush_darken)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.SOFT_LIGHT);
            mPaint.setColor(Color.rgb(50,50,50));
            colorModified = true;
            disableColorWheel = true;
        } else if (item == resources.getString(R.string.brush_haze)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.LUMINOSITY);
            mPaint.setColor(Color.rgb(GREYSCALE,GREYSCALE,GREYSCALE));
            colorModified = true;
            disableColorWheel = true;
        } else if (item == resources.getString(R.string.brush_saturation)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.SATURATION);
            mPaint.setColor(Color.rgb(255,255,0));
            colorModified = true;
            disableColorWheel = true;
        } else if (item == resources.getString(R.string.brush_erase)){
            selectedBrush = "Brush: " + item;
            mPaint.setBlendMode(BlendMode.DST);
            disableColorWheel = true;
        }
        invalidate();
    }

    public void setStrokeSize(int size){
        mBitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        if (mPaint.getStrokeWidth() != size)
            mPaint.setStrokeWidth((float)size);
        invalidate();
    }

    public void setBrushHardness(int x){
        mBitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        mPaintHardness = (float) x;
        mPaint.setMaskFilter(new BlurMaskFilter(mPaintHardness, BlurMaskFilter.Blur.NORMAL));
        invalidate();
    }

    public void setEyeDropperActive(boolean x){
        eyeDropperActive = x;
    }

    /********************************************************************
     *
     * DEALING WITH TOUCH
     *
     ******************************************************************/

    /** when ACTION_DOWN start touch according to the x,y values **/
    private void startTouch(float x, float y) {
        mPath.moveTo(x, y);
        mX = x;
        mY = y;
        deleteRedo();
    }

    /** when ACTION_MOVE move touch according to the x,y values **/
    private void moveTouchBrush(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOLERANCE || dy >= TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
        int touchedRGB = mBitmap.getPixel((int)dx, (int)dy);
        int r = Color.red(touchedRGB);
        int g = Color.green(touchedRGB);
        int b = Color.blue(touchedRGB);
        int avg = (r+b+g)/3;
        float oldHue = mColorHSV[VALUE];
        Color.RGBToHSV(avg,avg,avg,mColorHSV);
        mColorHSV[VALUE] = oldHue;
    }
    private void moveTouch(float x, float y){
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOLERANCE || dy >= TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
    }
    /** when ACTION_UP stop touch **/
    private void upTouch() {
        mPath.lineTo(mX, mY);

        /**SETTING UP UNDO AND REDO**/
        mCanvas.drawPath(mPath, mPaint);
        mPathList.add(mPath);
        mPaintList.add(mPaint);
        mPath = new Path();
        mPaint = new Paint();
        indexPathPaintList++;
        mPaint.set(mPaintList.get(indexPathPaintList-1));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (eyeDropperActive) {
            int x = (int) event.getX();
            int y = (int) event.getY();
            if (x > 0 && y > 0 && y < mBitmap.getHeight() && x < mBitmap.getWidth()) {
                getTouchedColor((int) event.getX(), (int) event.getY());
            }
        } else {
            float x = event.getX();
            float y = event.getY();

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    startTouch(x, y);
                    invalidate();
                    break;
                case MotionEvent.ACTION_MOVE:
                    moveTouch(x, y);
                    invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    upTouch();
                    invalidate();
                    break;
            }
        } return true;
    }

    /********************************************************************
     *
     * MISC FUNCTIONS
     *
     ******************************************************************/

    /****************************************************************************
     * undoStroke
     * removes the last stroke from the canvas (and adds it to the past list in
     * case the user needs to click redo
     ***************************************************************************/
    public void undoStroke(){
        //if (mPathList.size()> 0) {
        if (indexPathPaintList>0){
            mPastPathList.add(mPathList.get(mPathList.size()-1));
            mPathList.remove(mPathList.size()-1);
            mPastPaintList.add(mPaintList.get(mPaintList.size()-1));
            mPaintList.remove(mPaintList.size()-1);
            indexPastPathPaintList++;
            indexPathPaintList--;
            invalidate();
        }
    }

    /****************************************************************************
     * getTouchedColor
     * gets the color the user touches on the canvas
     ***************************************************************************/
    public void getTouchedColor(int x, int y){
        setDrawingCacheEnabled(true);
        buildDrawingCache();
        Bitmap bt = getDrawingCache();
        int touchedRGB = bt.getPixel(x,y);
        int r = Color.red(touchedRGB);
        int g = Color.green(touchedRGB);
        int b = Color.blue(touchedRGB);
        colorModified = true;
        Color.RGBToHSV(r, g, b, mColorHSV);
        setColor(mColorHSV);
        CanvasActivity.setCurrentColor(Color.HSVToColor(mColorHSV));;
    }

    /****************************************************************************
     * redoStroke
     * redraws a stroke that was previously undid
     ***************************************************************************/
    public void redoStroke(){
        if (mPastPathList.size() > 0){
            mPathList.add(mPastPathList.get(mPastPathList.size()-1));
            mPastPathList.remove(mPastPathList.size()-1);
            mPaintList.add(mPastPaintList.get(mPastPaintList.size()-1));
            mPastPaintList.remove(mPastPaintList.size()-1);
            indexPastPathPaintList--;
            indexPathPaintList++;
            invalidate();
        }
    }

    /****************************************************************************
     * clearCanvas
     * clears lists of Paths and Paint on the canvas
     ***************************************************************************/
    public void clearCanvas() {
        mPaintList.clear();
        mPathList.clear();
        indexPathPaintList = 0;
        deleteRedo();
        invalidate();
    }

    /****************************************************************************
     * deleteRedo
     * clear lists and index relating to using the redo button
     ***************************************************************************/
    private void deleteRedo(){
        mPastPathList.clear();
        mPastPaintList.clear();
        indexPastPathPaintList = 0;
    }
}