package com.lemenzo.gallery.editor;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
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;

/** Draggable freeform crop rectangle. Selection coordinates are normalized to the view. */
public final class CropOverlayView extends View {
    public interface Listener { void onCropChanged(RectF normalized, boolean finished); }
    private final Paint shade = new Paint();
    private final Paint border = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint handle = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final RectF rect = new RectF(.08f, .08f, .92f, .92f);
    private Listener listener;
    private int imageWidth = 1, imageHeight = 1;
    private int drag = -1;
    private float lastX, lastY;
    private static final float MIN = .08f;
    private static final int NODE_TOP_LEFT=1, NODE_TOP_RIGHT=2, NODE_BOTTOM_LEFT=3, NODE_BOTTOM_RIGHT=4, NODE_AREA=5;
    private static final int A_LEFT=0x01010101, A_RIGHT=0x01010102, A_UP=0x01010103, A_DOWN=0x01010104;
    private final AccessibilityNodeProvider accessibilityProvider = new CropNodeProvider();

    public CropOverlayView(Context c) { super(c); init(); }
    public CropOverlayView(Context c, AttributeSet a) { super(c,a); init(); }
    public CropOverlayView(Context c, AttributeSet a, int s) { super(c,a,s); init(); }
    private void init() {
        shade.setColor(0x99000000);
        border.setColor(Color.WHITE); border.setStyle(Paint.Style.STROKE); border.setStrokeWidth(3f);
        handle.setColor(Color.WHITE); handle.setStyle(Paint.Style.FILL);
        setClickable(true); setFocusable(true); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
    }
    public void setListener(Listener l) { listener = l; }
    public void setImageDimensions(int width, int height) { imageWidth = Math.max(1,width); imageHeight = Math.max(1,height); invalidate(); }
    public RectF getSelection() { return new RectF(rect); }
    public void resetSelection() { rect.set(.08f,.08f,.92f,.92f); 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); }

    @Override protected void onDraw(Canvas c) {
        super.onDraw(c);
        float w=getWidth(), h=getHeight(); if (w<=0||h<=0) return;
        RectF content=contentRect(); if(content.isEmpty()) return;
        RectF px = new RectF(content.left+rect.left*content.width(), content.top+rect.top*content.height(), content.left+rect.right*content.width(), content.top+rect.bottom*content.height());
        c.drawRect(0,0,w,px.top,shade); c.drawRect(0,px.bottom,w,h,shade);
        c.drawRect(0,px.top,px.left,px.bottom,shade); c.drawRect(px.right,px.top,w,px.bottom,shade);
        c.drawRect(px,border);
        float r=12f;
        c.drawCircle(px.left,px.top,r,handle); c.drawCircle(px.right,px.top,r,handle);
        c.drawCircle(px.left,px.bottom,r,handle); c.drawCircle(px.right,px.bottom,r,handle);
    }

    private int nearest(float x, float y) {
        RectF content=contentRect(); float w=content.width(),h=content.height(), threshold=56f; float best=Float.MAX_VALUE; int idx=-1;
        float[][] p={{content.left+rect.left*w,content.top+rect.top*h},{content.left+rect.right*w,content.top+rect.top*h},{content.left+rect.left*w,content.top+rect.bottom*h},{content.left+rect.right*w,content.top+rect.bottom*h}};
        for(int i=0;i<4;i++){float dx=x-p[i][0],dy=y-p[i][1],d=dx*dx+dy*dy;if(d<best&&d<threshold*threshold){best=d;idx=i;}}
        RectF px=new RectF(content.left+rect.left*w,content.top+rect.top*h,content.left+rect.right*w,content.top+rect.bottom*h); if(idx<0&&px.contains(x,y)) idx=4; return idx;
    }


    @Override public AccessibilityNodeProvider getAccessibilityNodeProvider() { return accessibilityProvider; }

    private final class CropNodeProvider extends AccessibilityNodeProvider {
        @Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
            if (virtualViewId == HOST_VIEW_ID) {
                AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain(CropOverlayView.this);
                root.setClassName(CropOverlayView.class.getName());
                root.setPackageName(getContext().getPackageName());
                root.setSource(CropOverlayView.this);
                root.setContentDescription(getContentDescription());
                root.setEnabled(isEnabled()); root.setFocusable(true);
                for (int id=NODE_TOP_LEFT; id<=NODE_AREA; id++) root.addChild(CropOverlayView.this,id);
                return root;
            }
            if (virtualViewId < NODE_TOP_LEFT || virtualViewId > NODE_AREA) return null;
            AccessibilityNodeInfo n = AccessibilityNodeInfo.obtain();
            n.setSource(CropOverlayView.this, virtualViewId); n.setParent(CropOverlayView.this);
            n.setPackageName(getContext().getPackageName()); n.setClassName("android.widget.Button");
            n.setEnabled(isEnabled()); n.setFocusable(true); n.setVisibleToUser(isShown());
            n.setContentDescription(nodeDescription(virtualViewId)); n.setBoundsInParent(nodeBounds(virtualViewId));
            n.addAction(new AccessibilityNodeInfo.AccessibilityAction(A_LEFT,getContext().getString(com.lemenzo.gallery.R.string.crop_move_left)));
            n.addAction(new AccessibilityNodeInfo.AccessibilityAction(A_RIGHT,getContext().getString(com.lemenzo.gallery.R.string.crop_move_right)));
            n.addAction(new AccessibilityNodeInfo.AccessibilityAction(A_UP,getContext().getString(com.lemenzo.gallery.R.string.crop_move_up)));
            n.addAction(new AccessibilityNodeInfo.AccessibilityAction(A_DOWN,getContext().getString(com.lemenzo.gallery.R.string.crop_move_down)));
            n.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
            n.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
            return n;
        }
        @Override public boolean performAction(int virtualViewId,int action,Bundle args) {
            if (virtualViewId < NODE_TOP_LEFT || virtualViewId > NODE_AREA) return false;
            if (action==AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS || action==AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
                sendVirtualEvent(virtualViewId, action==AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ? android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED : android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
                return true;
            }
            float dx=action==A_LEFT?-.025f:action==A_RIGHT?.025f:0f, dy=action==A_UP?-.025f:action==A_DOWN?.025f:0f;
            if(dx==0f&&dy==0f)return false;
            adjustNode(virtualViewId,dx,dy); changed(); sendVirtualEvent(virtualViewId,android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); return true;
        }
    }
    private void sendVirtualEvent(int virtualId,int type){
        if(!isShown())return;
        android.view.accessibility.AccessibilityEvent e=android.view.accessibility.AccessibilityEvent.obtain(type);
        e.setPackageName(getContext().getPackageName()); e.setClassName(CropOverlayView.class.getName()); e.setSource(this,virtualId);
        android.view.ViewParent parent=getParent(); if(parent!=null)parent.requestSendAccessibilityEvent(this,e);
    }
    private CharSequence nodeDescription(int id){
        int r=id==NODE_TOP_LEFT?com.lemenzo.gallery.R.string.crop_handle_top_left:id==NODE_TOP_RIGHT?com.lemenzo.gallery.R.string.crop_handle_top_right:id==NODE_BOTTOM_LEFT?com.lemenzo.gallery.R.string.crop_handle_bottom_left:id==NODE_BOTTOM_RIGHT?com.lemenzo.gallery.R.string.crop_handle_bottom_right:com.lemenzo.gallery.R.string.crop_area;
        return getContext().getString(r);
    }
    private Rect nodeBounds(int id){
        RectF c=contentRect(); float x1=c.left+rect.left*c.width(),y1=c.top+rect.top*c.height(),x2=c.left+rect.right*c.width(),y2=c.top+rect.bottom*c.height();
        if(id==NODE_AREA)return new Rect(Math.round(x1),Math.round(y1),Math.round(x2),Math.round(y2));
        float x=(id==NODE_TOP_LEFT||id==NODE_BOTTOM_LEFT)?x1:x2, y=(id==NODE_TOP_LEFT||id==NODE_TOP_RIGHT)?y1:y2; int r=40;
        return new Rect(Math.round(x)-r,Math.round(y)-r,Math.round(x)+r,Math.round(y)+r);
    }
    private void adjustNode(int id,float dx,float dy){
        if(id==NODE_AREA){rect.offset(dx,dy); if(rect.left<0)rect.offset(-rect.left,0);if(rect.right>1)rect.offset(1-rect.right,0);if(rect.top<0)rect.offset(0,-rect.top);if(rect.bottom>1)rect.offset(0,1-rect.bottom);return;}
        if(id==NODE_TOP_LEFT||id==NODE_BOTTOM_LEFT)rect.left=Math.max(0,Math.min(rect.left+dx,rect.right-MIN)); else rect.right=Math.min(1,Math.max(rect.right+dx,rect.left+MIN));
        if(id==NODE_TOP_LEFT||id==NODE_TOP_RIGHT)rect.top=Math.max(0,Math.min(rect.top+dy,rect.bottom-MIN)); else rect.bottom=Math.min(1,Math.max(rect.bottom+dy,rect.top+MIN));
    }

    @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(CropOverlayView.class.getName());
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01010001, getContext().getString(com.lemenzo.gallery.R.string.crop_move_left)));
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01010002, getContext().getString(com.lemenzo.gallery.R.string.crop_move_right)));
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01010003, getContext().getString(com.lemenzo.gallery.R.string.crop_move_up)));
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01010004, getContext().getString(com.lemenzo.gallery.R.string.crop_move_down)));
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01010005, getContext().getString(com.lemenzo.gallery.R.string.crop_shrink)));
        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(0x01010006, getContext().getString(com.lemenzo.gallery.R.string.crop_expand)));
    }
    @Override public boolean performAccessibilityAction(int action, Bundle args) {
        float d=.025f;
        if(action>=0x01010001&&action<=0x01010004){float dx=action==0x01010001?-d:action==0x01010002?d:0,dy=action==0x01010003?-d:action==0x01010004?d:0;rect.offset(dx,dy);if(rect.left<0)rect.offset(-rect.left,0);if(rect.right>1)rect.offset(1-rect.right,0);if(rect.top<0)rect.offset(0,-rect.top);if(rect.bottom>1)rect.offset(0,1-rect.bottom);changed();return true;}
        if(action==0x01010005||action==0x01010006){float delta=action==0x01010005?.025f:-.025f;RectF n=new RectF(rect.left+delta,rect.top+delta,rect.right-delta,rect.bottom-delta);if(n.width()>=MIN&&n.height()>=MIN&&n.left>=0&&n.top>=0&&n.right<=1&&n.bottom<=1){rect.set(n);changed();}return true;}
        return super.performAccessibilityAction(action,args);
    }
    private void changed(){invalidate();if(listener!=null)listener.onCropChanged(new RectF(rect),true);announceForAccessibility(getContentDescription());}

    @Override public boolean onTouchEvent(MotionEvent e) {
        if (!isEnabled()) return false;
        RectF content=contentRect(); float w=Math.max(1,content.width()), h=Math.max(1,content.height());
        if(e.getActionMasked()==MotionEvent.ACTION_DOWN){drag=nearest(e.getX(),e.getY());lastX=e.getX();lastY=e.getY();return drag>=0;}
        if(drag<0)return false;
        if(e.getActionMasked()==MotionEvent.ACTION_MOVE){
            float nx=Math.max(0f,Math.min(1f,(e.getX()-content.left)/w)), ny=Math.max(0f,Math.min(1f,(e.getY()-content.top)/h));
            if(drag==0){rect.left=Math.min(nx,rect.right-MIN);rect.top=Math.min(ny,rect.bottom-MIN);}
            else if(drag==1){rect.right=Math.max(nx,rect.left+MIN);rect.top=Math.min(ny,rect.bottom-MIN);}
            else if(drag==2){rect.left=Math.min(nx,rect.right-MIN);rect.bottom=Math.max(ny,rect.top+MIN);}
            else if(drag==3){rect.right=Math.max(nx,rect.left+MIN);rect.bottom=Math.max(ny,rect.top+MIN);}
            else {float dx=(e.getX()-lastX)/w,dy=(e.getY()-lastY)/h; float rw=rect.width(),rh=rect.height(); rect.offset(dx,dy); if(rect.left<0)rect.offset(-rect.left,0);if(rect.top<0)rect.offset(0,-rect.top);if(rect.right>1)rect.offset(1-rect.right,0);if(rect.bottom>1)rect.offset(0,1-rect.bottom);rect.right=rect.left+rw;rect.bottom=rect.top+rh;}
            lastX=e.getX();lastY=e.getY();invalidate();if(listener!=null)listener.onCropChanged(new RectF(rect),false);return true;
        }
        if(e.getActionMasked()==MotionEvent.ACTION_UP||e.getActionMasked()==MotionEvent.ACTION_CANCEL){if(listener!=null)listener.onCropChanged(new RectF(rect),true);drag=-1;return true;}
        return true;
    }
}
