package com.lemenzo.gallery.editor;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/** On-device markup surface. Coordinates are normalized to the displayed image content. */
public final class AnnotationOverlayView extends View {
    public static final int MODE_NONE = 0;
    public static final int MODE_DRAW = 1;
    public static final int MODE_REDACT = 2;

    public interface Listener {
        void onAnnotationActionStart();
        void onAnnotationActionEnd();
    }

    private final Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint redactionPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final ArrayList<EditorAnnotation> annotations = new ArrayList<>();
    private final ArrayList<Float> active = new ArrayList<>();
    private int mode = MODE_NONE;
    private int imageWidth = 1;
    private int imageHeight = 1;
    private Listener listener;
    private static final int ACTION_REMOVE = 0x01012001;
    private final AccessibilityNodeProvider accessibilityProvider = new AnnotationNodeProvider();

    public AnnotationOverlayView(Context context) { super(context); init(); }
    public AnnotationOverlayView(Context context, AttributeSet attrs) { super(context, attrs); init(); }
    public AnnotationOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); }

    private void init() {
        setClickable(true);
        setFocusable(true);
        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        strokePaint.setColor(Color.WHITE);
        strokePaint.setStyle(Paint.Style.STROKE);
        strokePaint.setStrokeCap(Paint.Cap.ROUND);
        strokePaint.setStrokeJoin(Paint.Join.ROUND);
        redactionPaint.setColor(Color.BLACK);
        redactionPaint.setStyle(Paint.Style.FILL);
        textPaint.setColor(Color.WHITE);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setShadowLayer(4f, 0f, 1f, Color.BLACK);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public void setListener(Listener listener) { this.listener = listener; }
    public void setMode(int mode) { this.mode = mode; active.clear(); invalidate(); }
    public int getMode() { return mode; }
    public void setImageDimensions(int width, int height) { imageWidth = Math.max(1, width); imageHeight = Math.max(1, height); invalidate(); }

    public List<EditorAnnotation> getAnnotations() {
        ArrayList<EditorAnnotation> copy = new ArrayList<>();
        for (EditorAnnotation a : annotations) copy.add(new EditorAnnotation(a));
        return copy;
    }

    public void setAnnotations(List<EditorAnnotation> values) {
        annotations.clear();
        if (values != null) for (EditorAnnotation a : values) annotations.add(new EditorAnnotation(a));
        active.clear();
        invalidate();
    }

    public void clearAnnotations() { annotations.clear(); active.clear(); invalidate(); }

    public void addCenteredText(String text) {
        if (text == null || text.trim().isEmpty()) return;
        annotations.add(new EditorAnnotation(EditorAnnotation.TYPE_TEXT,
                java.util.Arrays.asList(.5f, .5f), text.trim()));
        invalidate();
    }

    private RectF contentRect() {
        float w = getWidth(), h = getHeight();
        if (w <= 0 || h <= 0) return new RectF();
        float scale = Math.min(w / imageWidth, h / imageHeight);
        float cw = imageWidth * scale, ch = imageHeight * scale;
        return new RectF((w - cw) / 2f, (h - ch) / 2f, (w + cw) / 2f, (h + ch) / 2f);
    }

    private float nx(float x, RectF r) { return Math.max(0f, Math.min(1f, (x - r.left) / Math.max(1f, r.width()))); }
    private float ny(float y, RectF r) { return Math.max(0f, Math.min(1f, (y - r.top) / Math.max(1f, r.height()))); }


    @Override public AccessibilityNodeProvider getAccessibilityNodeProvider(){ return accessibilityProvider; }
    private final class AnnotationNodeProvider extends AccessibilityNodeProvider {
        @Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId){
            if(virtualViewId==HOST_VIEW_ID){AccessibilityNodeInfo root=AccessibilityNodeInfo.obtain(AnnotationOverlayView.this);root.setSource(AnnotationOverlayView.this);root.setPackageName(getContext().getPackageName());root.setClassName(AnnotationOverlayView.class.getName());for(int i=0;i<annotations.size();i++)root.addChild(AnnotationOverlayView.this,i+1);return root;}
            int index=virtualViewId-1; if(index<0||index>=annotations.size())return null; EditorAnnotation a=annotations.get(index);
            AccessibilityNodeInfo n=AccessibilityNodeInfo.obtain();n.setSource(AnnotationOverlayView.this,virtualViewId);n.setParent(AnnotationOverlayView.this);n.setPackageName(getContext().getPackageName());n.setClassName("android.view.View");n.setFocusable(true);n.setVisibleToUser(isShown());n.setEnabled(isEnabled());n.setContentDescription(annotationDescription(a,index));n.setBoundsInParent(annotationBounds(a));n.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_REMOVE,getContext().getString(com.lemenzo.gallery.R.string.accessibility_remove_annotation)));return n;
        }
        @Override public boolean performAction(int virtualViewId,int action,Bundle args){int index=virtualViewId-1;if(index<0||index>=annotations.size())return false;if(action==ACTION_REMOVE){if(listener!=null)listener.onAnnotationActionStart();annotations.remove(index);invalidate();if(listener!=null)listener.onAnnotationActionEnd();return true;}return false;}
    }
    private CharSequence annotationDescription(EditorAnnotation a,int index){int id=a.type==EditorAnnotation.TYPE_REDACTION?com.lemenzo.gallery.R.string.accessibility_redaction_item:a.type==EditorAnnotation.TYPE_TEXT?com.lemenzo.gallery.R.string.accessibility_text_item:com.lemenzo.gallery.R.string.accessibility_drawing_item;return getContext().getString(id,index+1);}
    private Rect annotationBounds(EditorAnnotation a){RectF c=contentRect();if(a.points==null||a.points.isEmpty())return new Rect();float minX=1,minY=1,maxX=0,maxY=0;for(int i=0;i+1<a.points.size();i+=2){float x=a.points.get(i),y=a.points.get(i+1);minX=Math.min(minX,x);maxX=Math.max(maxX,x);minY=Math.min(minY,y);maxY=Math.max(maxY,y);}if(a.type==EditorAnnotation.TYPE_TEXT){minX-=.12f;maxX+=.12f;minY-=.08f;maxY+=.04f;}return new Rect(Math.round(c.left+Math.max(0,minX)*c.width()),Math.round(c.top+Math.max(0,minY)*c.height()),Math.round(c.left+Math.min(1,maxX)*c.width()),Math.round(c.top+Math.min(1,maxY)*c.height()));}

    @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01011001, getContext().getString(com.lemenzo.gallery.R.string.accessibility_add_redaction)));
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01011002, getContext().getString(com.lemenzo.gallery.R.string.accessibility_add_text)));
    }
    @Override public boolean performAccessibilityAction(int action, Bundle args) {
        if(action==0x01011001){if(listener!=null)listener.onAnnotationActionStart();annotations.add(new EditorAnnotation(EditorAnnotation.TYPE_REDACTION,java.util.Arrays.asList(.35f,.42f,.65f,.58f),null));invalidate();if(listener!=null)listener.onAnnotationActionEnd();return true;}
        if(action==0x01011002){if(listener!=null)listener.onAnnotationActionStart();addCenteredText("Text");if(listener!=null)listener.onAnnotationActionEnd();return true;}
        return super.performAccessibilityAction(action,args);
    }

    @Override public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) return false;
        if (mode == MODE_NONE) return false;
        RectF r = contentRect();
        if (r.isEmpty()) return false;
        float x = Math.max(r.left, Math.min(r.right, event.getX()));
        float y = Math.max(r.top, Math.min(r.bottom, event.getY()));
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            if (!r.contains(event.getX(), event.getY())) return false;
            active.clear(); active.add(nx(x, r)); active.add(ny(y, r));
            if (listener != null) listener.onAnnotationActionStart();
            invalidate(); return true;
        }
        if (active.isEmpty()) return false;
        if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
            if (mode == MODE_DRAW) { active.add(nx(x, r)); active.add(ny(y, r)); }
            else if (mode == MODE_REDACT) {
                while (active.size() > 2) active.remove(active.size() - 1);
                active.add(nx(x, r)); active.add(ny(y, r));
            }
            invalidate(); return true;
        }
        if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
            if (mode == MODE_DRAW && active.size() >= 4) annotations.add(new EditorAnnotation(EditorAnnotation.TYPE_STROKE, active, null));
            else if (mode == MODE_REDACT && active.size() >= 4) annotations.add(new EditorAnnotation(EditorAnnotation.TYPE_REDACTION, active, null));
            active.clear(); invalidate();
            if (listener != null) listener.onAnnotationActionEnd();
            return true;
        }
        return true;
    }

    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        RectF r = contentRect();
        if (r.isEmpty()) return;
        strokePaint.setStrokeWidth(Math.max(3f, r.width() * .006f));
        textPaint.setTextSize(Math.max(24f, r.width() * .055f));
        for (EditorAnnotation a : annotations) drawAnnotation(canvas, r, a);
        if (!active.isEmpty()) {
            int type = mode == MODE_DRAW ? EditorAnnotation.TYPE_STROKE : EditorAnnotation.TYPE_REDACTION;
            drawAnnotation(canvas, r, new EditorAnnotation(type, active, null));
        }
    }

    private void drawAnnotation(Canvas canvas, RectF r, EditorAnnotation a) {
        if (a == null || a.points == null) return;
        if (a.type == EditorAnnotation.TYPE_STROKE && a.points.size() >= 4) {
            Path p = new Path();
            p.moveTo(r.left + a.points.get(0) * r.width(), r.top + a.points.get(1) * r.height());
            for (int i = 2; i + 1 < a.points.size(); i += 2)
                p.lineTo(r.left + a.points.get(i) * r.width(), r.top + a.points.get(i + 1) * r.height());
            canvas.drawPath(p, strokePaint);
        } else if (a.type == EditorAnnotation.TYPE_REDACTION && a.points.size() >= 4) {
            float x1 = r.left + a.points.get(0) * r.width(), y1 = r.top + a.points.get(1) * r.height();
            float x2 = r.left + a.points.get(2) * r.width(), y2 = r.top + a.points.get(3) * r.height();
            canvas.drawRect(Math.min(x1,x2), Math.min(y1,y2), Math.max(x1,x2), Math.max(y1,y2), redactionPaint);
        } else if (a.type == EditorAnnotation.TYPE_TEXT && a.points.size() >= 2 && a.text != null) {
            float x = r.left + a.points.get(0) * r.width(), y = r.top + a.points.get(1) * r.height();
            float tw = textPaint.measureText(a.text);
            canvas.drawText(a.text, x - tw / 2f, y, textPaint);
        }
    }
}
