package org.onebillion.onecourse.mainui.oc_playzone;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.SystemClock;
import android.view.View;

import org.onebillion.onecourse.controls.OBControl;
import org.onebillion.onecourse.controls.OBGroup;
import org.onebillion.onecourse.controls.OBImage;
import org.onebillion.onecourse.controls.OBPath;
import org.onebillion.onecourse.mainui.OC_SectionController;
import org.onebillion.onecourse.utils.OBConditionLock;
import org.onebillion.onecourse.utils.OBTimer;
import org.onebillion.onecourse.utils.OBUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class OC_PlayZoneTrace extends OC_SectionController
{
    String letter;
    float letterrecth;
    float dashlen0,dashlen1,lineWidth;
    OBImage blobImage;
    OBControl mask;
    Bitmap bitmapContext,bitmapContextR;
    List<OBGroup> groupList;
    boolean maskNeedsUpdate;
    OBTimer timer;
    double startTime;
    OBImage back,redLayer;
    OBControl hollow;
    OBConditionLock traceLock;
    float spacerwidth;
    int fillablePixelCount;
    int score;


    public String layoutName()
    {
        return "mastera";
    }

    public String tracingFileName()
    {
        return "tracingcapitalletters";
    }

    public long switchStatus(String scene)
    {
        return setStatus(STATUS_WAITING_FOR_TRACE);
    }

    public void miscSetUp()
    {
        groupList = new ArrayList<>();
        letter = OBUtils.coalesce(parameters.get("letter") , "c");
        events = new ArrayList<>(Arrays.asList("a","b","remaining"));
        Map ed = loadXML(getConfigPath(String.format("%s.xml",tracingFileName())));
        eventsDict.putAll(ed);
        loadEvent("masterl");
        letterrecth = objectDict.get("letterrect").height();
        deleteControls("letterrect");
        OBControl spacer = objectDict.get("spacer");
        spacerwidth = spacer.width();
        OBControl blob = objectDict.get("blob");
        blobImage = blob.renderedImageControl();
        deleteControls("blob");
        traceLock = new OBConditionLock(0);
    }

    public void prepare()
    {
        super.prepare();
        loadEvent(layoutName());
        loadFingers();
        miscSetUp();
        doVisual(currentEvent());
    }

    public void start()
    {
        setStatus(0);
        OBUtils.runOnOtherThread(new OBUtils.RunLambda()
        {
            public void run() throws Exception
            {
                try
                {
                    if(!performSel("demo",currentEvent()))
                    {
                        doBody(currentEvent());
                    }
                }
                catch(Exception exception) {
                }
            }
        });
    }

    public void endBody()
    {
        final long stt = statusTime();
        reprompt(stt, null, 6, new OBUtils.RunLambda()
                {
                    @Override
                    public void run() throws Exception
                    {
                        flashRemind(stt);
                    }
                });
    }


    public void cleanUpTrace()
    {
        if(bitmapContext !=null)
        {
            bitmapContext = null;
        }
        if(bitmapContextR != null)
        {
            bitmapContextR = null;
        }
        if(back != null)
            detachControl(back);
        if(redLayer != null)
            detachControl(redLayer);
    }

    public int countFilledPixels(Bitmap bcontext)
    {
        int w = (bcontext).getWidth();
        int h = (bcontext).getHeight();
        int tot = 0;
        int pixels[] = new int[w];
        for(int i = 0;i < h;i++)
        {
            bcontext.getPixels(pixels, 0, w, 0, i, w, 1);
            for(int j = 0;j < w;j++)
            {
                int px = pixels[j];
                if(Color.red(px) > 25)
                {
                    tot++;
                }
            }
        }
        return tot;
    }

    static int BACK_ZPOS = 50;

    public void setUpTraceLetter(int idx)
    {
        cleanUpTrace();
        traceLock.lock();
        traceLock.unlockWithCondition(0);
        startTime = 0;
        setLetterDashed(idx,false);
        hollow = hollowForGroup(groupList.get(idx));
        RectF f = new RectF(hollow.bounds());
        hollow.setFrame(f);
        RectF b = new RectF(f);

        bitmapContext = Bitmap.createBitmap((int)b.width(),(int) b.height(),Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmapContext);
        canvas.drawARGB(255,0,0,0);
        hollow.drawLayer(canvas,0);

        back = new OBImage(bitmapContext);
        back.setBounds(b);
        back.setPosition(groupList.get(idx).position());
        back.setZPosition(BACK_ZPOS);
        fillablePixelCount = countFilledPixels(bitmapContext);

        bitmapContextR = Bitmap.createBitmap((int)b.width(),(int) b.height(),Bitmap.Config.ARGB_8888);
        redLayer = new OBImage(bitmapContextR);
        redLayer.setBounds(b);
        redLayer.setPosition(groupList.get(idx).position());
        redLayer.setZPosition(BACK_ZPOS + 1);

        back.setMaskControl(hollow);
        //hollow.hide();
        attachControl(back);

        attachControl(redLayer);
    }


    public OBGroup letterGroup(String l,String rectName)
    {
        OBControl lrect = objectDict.get(rectName);
        float hfrac = lrect.height() / letterrecth;
        objectDict.put("letterrect",lrect);
        List arr = loadEvent(l);
        List memberlist = new ArrayList<>();
        //RectF f = new RectF();
        RectF f = null;
        for(OBControl c : filterControls("Path.*"))
        {
            if(arr.contains(c))
            {
                OBPath p =(OBPath) c;
                float lw = p.lineWidth() * hfrac;
                p.setLineWidth(lw);
                if(dashlen0 == 0)
                {
                    dashlen0 = 0.01f;
                    dashlen1 = lw * 0.5f * 1.2f;
                    lineWidth = lw;
                }
                memberlist.add(p);
                RectF f2 = p.boundingBox();
                //f.union(f2);
                f = OBUtils.corRectUnion(f,f2);
            }
        }
        f.inset(-lineWidth,-lineWidth);
        OBGroup g = new OBGroup(memberlist,f);
        return g;
    }


    public void setLetterDashed(int letteridx,boolean dashed)
    {
        List<OBPath>pathList = (List)groupList.get(letteridx).members;
        int col = dashed?Color.argb(255,230,230,230) :Color.BLACK;
        float lw = dashed?lineWidth * 0.5f:lineWidth;
        lockScreen();
        for(OBPath p : pathList)
        {
            if(dashed)
                p.setLineDashPattern(Arrays.asList((dashlen0) ,(dashlen1)));
            else
                p.setLineDashPattern(null);
            p.setStrokeColor(col);
            p.setLineWidth(lw);
        }
        unlockScreen();
    }

    public void setLetterHigh(int letteridx,boolean high)
    {
        List<OBPath>pathList = (List)groupList.get(letteridx).members;
        int col = high?Color.BLUE :Color.BLACK;
        lockScreen();
        for(OBPath p : pathList)
            p.setStrokeColor(col);
        unlockScreen();
    }

    public OBGroup hollowGroup(OBGroup gp)
    {
        OBGroup newgroup = (OBGroup)gp.copy();
        float strokediff = applyGraphicScale(3.0f);
        for(OBPath p : (List<OBPath>)(List)newgroup.members)
        {
            p.setStrokeColor(Color.WHITE);
            p.setLineWidth(p.lineWidth() - strokediff);
        }
        return newgroup;
    }

    public OBGroup hollowForGroup(OBGroup gp)
    {
        OBGroup phollow = (OBGroup) gp.propertyValue("hollow");
        if(phollow == null)
        {
            phollow = hollowGroup(gp);
            gp.setProperty("hollow",phollow);
            phollow.setZPosition(gp.zPosition() +1);
            //attachControl(phollow);
        }
        return phollow;
    }

    public void setLetterHollow(int letteridx,boolean hollow)
    {
        OBGroup gp = groupList.get(letteridx);
        OBGroup phollow = hollowForGroup(gp);
        phollow.setHidden(!hollow);
    }


    public void playLetterSound(String s)
    {
        playAudio(String.format("is_%s",s));
    }

    public void highlightAndPlay(int i) throws Exception
    {
        setLetterHigh(i,true);
        waitForSecs(0.3f);
        playLetterSound(letter);
        waitForSecs(0.2f);
        waitAudio();
        waitForSecs(0.5f);
        setLetterHigh(i,false);
    }

    public void updateBack()
    {
        lockScreen();
        back.setContents(bitmapContext);
        back.setNeedsRetexture();
        back.invalidate();
        unlockScreen();
    }

    public void doFrame(OBTimer tmr)
    {
        if(maskNeedsUpdate)
        {
            updateBack();
            maskNeedsUpdate = false;
        }
    }

    public void startTimer()
    {
        if (timer != null)
            stopTimer();
        timer = new OBTimer(0.02f) {
            @Override
            public int timerEvent(OBTimer timer) {
                doFrame(timer);
                if (_aborting)
                    return 0;
                return 1;
            }
        };
        timer.scheduleTimerEvent();
    }

    public void stopTimer()
    {
        if (timer != null) {
            timer.invalidate();
            timer = null;
        }
    }

    public void cleanUp()
    {
        stopTimer();
        super.cleanUp();
    }

    public boolean pixelsLeft(float threshold)
    {
        int w = bitmapContext.getWidth();
        int h = bitmapContext.getHeight();
        int pixelThreshold = (int)(fillablePixelCount * threshold);
        int tot = 0;
        int pixels[] = new int[w];
        for(int i = 0;i < h;i++)
        {
            bitmapContext.getPixels(pixels, 0, w, 0, i, w, 1);
            for(int j = 0;j < w;j++)
            {
                int px = pixels[j];
                if (Color.red(px) > 25)
                    tot++;
                if(tot > pixelThreshold)
                    return false;
            }
        }
        return true;
    }



    public void showRedParts()
    {
        int w = bitmapContext.getWidth();
        int h = bitmapContext.getHeight();
        int pixels[] = new int[w];
        for(int i = 0;i < h;i++)
        {
            bitmapContext.getPixels(pixels, 0, w, 0, i, w, 1);
            for(int j = 0;j < w;j++)
            {
                int px = pixels[j];
                if (Color.red(px) > 25)
                    bitmapContextR.setPixel(j,i, Color.RED);
                else
                    bitmapContextR.setPixel(j,i, 0);
            }
        }
        redLayer.setContents(bitmapContextR);
    }

    public void flashRed() throws Exception
    {
        for(int i = 0;i < 2;i++)
        {
            waitForSecs(0.3f);
            lockScreen();
            redLayer.hide();
            unlockScreen();
            waitForSecs(0.3f);
            lockScreen();
            redLayer.show();
            unlockScreen();
        }
    }

    static int     TRACING = 0,
            TIME_DONE = 1;

    static float PIXEL_THRESHOLD = 0.1f;

    public void finishTrace(Map d)
    {
        try
        {
            traceLock.lockWhenCondition(TIME_DONE);
            traceLock.unlock();
            Integer n = (Integer)d.get("n");
            if(n != null && n.intValue() == currNo)
            {
                d.remove("n");
                if(!pixelsLeft(PIXEL_THRESHOLD))
                {
                    lockScreen();
                    showRedParts();
                    unlockScreen();
                    flashRed();
                }
                finishLetter();
            }
        }
        catch(Exception e)
        {
        }
    }

    public void timesUp(Map d)
    {
        Integer n = (Integer)d.get("n");
        if(n != null && n.intValue() == currNo)
        {
            setStatus(STATUS_CHECKING);
            traceLock.lock();
            int cond = traceLock.conditionValue();
            traceLock.unlockWithCondition(cond | TIME_DONE);
        }
    }

    public void stampImage(OBImage img,PointF point)
    {
        RectF f = back.bounds();
        Canvas canvas = new Canvas(bitmapContext);
        canvas.translate( point.x, point.y);
        canvas.scale(img.scaleX(),img.scaleY());
        canvas.translate(-img.width()/2f,-img.height()/2f);
        img.drawLayer(canvas,0);
        maskNeedsUpdate = true;
    }

    static int TIME_THRESHOLD = 10;

    public void flashLetter(long stt) throws Exception
    {
        for(int i = 0;i < 2;i++)
        {
            waitForSecs(0.3f);
            lockScreen();
            redLayer.show();
            unlockScreen();
            waitForSecs(0.3f);
            lockScreen();
            redLayer.hide();
            unlockScreen();
        }
    }

    public void flashRemind(final long stt)
    {
        if(statusTime() == stt)
        {
            try
            {
                lockScreen();
                showRedParts();
                unlockScreen();
                flashLetter(stt);
                OBUtils.runOnOtherThreadDelayed(10,new OBUtils.RunLambda()
                {
                    public void run() throws Exception
                    {
                        flashRemind(stt);
                    }
                });

            }
            catch(Exception e)
            {
            }
         }
    }

    public void nextLetter() throws Exception
    {
        if(++currNo >= groupList.size())
        {
            nextScene();
            return;
        }
        else
        {
            playSfxAudio("tap",false);
            lockScreen();
            setUpTraceLetter(currNo);
            unlockScreen();
            setStatus(STATUS_WAITING_FOR_TRACE);
            final long stt = statusTime();
            reprompt(stt, null, 5, new OBUtils.RunLambda()
            {
                @Override
                public void run() throws Exception
                {
                    flashRemind(stt);
                }
            });

        }
    }
    public void finishLetter() throws Exception
    {
        lockScreen();
        redLayer.hide();
        //hollow.hide();
        back.hide();
        unlockScreen();
        waitForSecs(0.4f);
        highlightAndPlay(currNo);
        waitForSecs(0.4f);
        nextLetter();
    }

    public void touchUpAtPoint(PointF pt,View v)
    {
        if(status() == STATUS_TRACING)
        {
            setStatus(STATUS_CHECKING);
            stopTimer();
            boolean done =(pixelsLeft(PIXEL_THRESHOLD));
            traceLock.lock();
            if(done)
                traceLock.unlockWithCondition(TIME_DONE);
            else
            {
                int cond = traceLock.conditionValue();
                traceLock.unlockWithCondition((cond & ~TRACING));
                setStatus(STATUS_WAITING_FOR_TRACE);
            }
        }
    }


    public void touchMovedToPoint(PointF pt,View v)
    {
        if(status() == STATUS_TRACING)
        {
            PointF tpt = convertPointToControl(pt,back);
            stampImage(blobImage,tpt);
        }
    }



    public void checkTraceStart(PointF pt)
    {
        setStatus(STATUS_CHECKING);
        startTimer();
        PointF tpt = convertPointToControl(pt,back);
        stampImage(blobImage,tpt);
        setStatus(STATUS_TRACING);
        Map trDict = new HashMap();
        trDict.put("n",currNo);
        final Map ftrDict = trDict;
        if(startTime == 0)
        {
            startTime = SystemClock.uptimeMillis();
            OBUtils.runOnOtherThreadDelayed(10,new OBUtils.RunLambda()
            {
                public void run() throws Exception
                {
                    timesUp(ftrDict);
                }
            });
            OBUtils.runOnOtherThread(new OBUtils.RunLambda()
            {
                public void run() throws Exception
                {
                    finishTrace(ftrDict);
                }
            });
        }
    }

    public void touchDownAtPoint(PointF pt,View v)
    {
        if(status() == STATUS_WAITING_FOR_TRACE)
        {
            traceLock.lock();
            int cond = traceLock.conditionValue();
            traceLock.unlockWithCondition(cond | TRACING);
            checkTraceStart(pt);
        }
    }

}