package org.onebillion.onecourse.mainui.generic;

import android.graphics.Color;
import android.graphics.PathMeasure;
import android.graphics.PointF;
import android.os.AsyncTask;
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.OBLabel;
import org.onebillion.onecourse.controls.OBPath;
import org.onebillion.onecourse.controls.OBStroke;
import org.onebillion.onecourse.mainui.MainActivity;
import org.onebillion.onecourse.mainui.OC_Tracer;
import org.onebillion.onecourse.utils.OBRunnableSyncUI;
import org.onebillion.onecourse.utils.OBUserPressedBackException;
import org.onebillion.onecourse.utils.OBUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * OC_Generic_Tracing
 * Generic Event for Units where the Child has to trace numbers.
 *
 * @param autoClean indicates if the tracing is cleared automatically after Child successfuly traces the number the first time or if it requires a touch to clear
 *                  Created by pedroloureiro on 12/07/16.
 */
public class OC_Generic_Tracing extends OC_Tracer
{
    protected static float tracingSpeed = 2.0f;
    //
    protected int currentDemoAudioIndex;
    protected int currentTry;
    //
    protected OBGroup path1, path2;
    protected OBImage dash1, dash2;
    protected OBControl trace_arrow;

    int savedStatus;
    List<Object> savedReplayAudio;
    public Boolean autoClean;
    public PointF lastPointAdded;


    public OC_Generic_Tracing (Boolean autoClean)
    {
        this.autoClean = autoClean;
    }


    public void pointer_demoTrace (Boolean playAudio) throws Exception
    {
        List<OBPath> paths = (List<OBPath>) (Object) path1.filterMembers("p[0-9]+", true);
        //
        PointF destination = tracing_position_arrow();
        movePointerToPoint(destination, -25, 0.6f, true);
        //
        if (playAudio)
        {
            action_playNextDemoSentence(false);
        }
        //
        for (OBPath path : paths)
        {
            pointer_traceAlongPath(path, tracingSpeed);
            //
            subPathIndex++;
            destination = tracing_position_arrow();
            if (destination != null)
            {
                movePointerToPoint(destination, -25, 0.3f, true);
            }
        }
        //
        if (path2 != null)
        {
            paths = (List<OBPath>) (Object) path2.filterMembers("p[0-9]+", true);
            //
            for (OBPath path : paths)
            {
                pointer_traceAlongPath(path, tracingSpeed);
                //
                subPathIndex++;
                destination = tracing_position_arrow();
                if (destination != null)
                {
                    movePointerToPoint(destination, -25, 0.3f, true);
                }
            }
        }
        if (playAudio)
        {
            waitAudio();
        }
    }


    public void action_answerIsCorrect () throws Exception
    {
        if (currentTry == 1)
        {
            gotItRightBigTick(false);
            waitSFX();
            waitForSecs(0.3);
            //
            playAudioQueuedScene(currentEvent(), "CORRECT", true);
            waitForSecs(0.7);
            //
            currentTry++;
            playAudioQueuedScene(currentEvent(), "PROMPT2", false);
            //
            if (autoClean)
            {
                lockScreen();
                tracing_reset();
                unlockScreen();
                //
                revertStatusAndReplayAudio();
                setStatus(STATUS_WAITING_FOR_TRACE);
            }
            else
            {
                revertStatusAndReplayAudio();
                setStatus(STATUS_AWAITING_CLICK);
            }
        }
        else
        {
            gotItRightBigTick(true);
            //
            playAudioQueuedScene(currentEvent(), "CORRECT", true);
            waitForSecs(0.3);
            //
            playAudioQueuedScene(currentEvent(), "FINAL", true);
            waitForSecs(0.3);
            //
            nextScene();
        }
    }


    public void prepare ()
    {
        super.prepare();
        loadFingers();
        loadEvent("master1");
        String scenes = (String) eventAttributes.get(action_getScenesProperty());
        if (scenes != null)
        {
            String[] eva = scenes.split(",");
            events = Arrays.asList(eva);
        }
        else
        {
            events = new ArrayList<>();
        }
        //
        if (currentEvent() != null)
        {
            doVisual(currentEvent());
        }
        pathColour = Color.BLUE;
    }


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


    public void start ()
    {
        final long timeStamp = switchStatus(currentEvent());
        //
        OBUtils.runOnOtherThread(new OBUtils.RunLambda()
        {
            @Override
            public void run () throws Exception
            {
                try
                {
                    if (!performSel("demo", currentEvent()))
                    {
                        doBody(currentEvent());
                    }
                    //
                    OBUtils.runOnOtherThreadDelayed(3, new OBUtils.RunLambda()
                    {
                        @Override
                        public void run () throws Exception
                        {
                            if (!statusChanged(timeStamp))
                            {
                                playAudioQueuedScene(currentEvent(), "REMIND", false);
                            }
                        }
                    });
                }
                catch (OBUserPressedBackException e)
                {
                    stopAllAudio();
                    throw e;
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
    }


    public void setSceneXX (String scene)
    {
        ArrayList<OBControl> oldControls = new ArrayList<>(objectDict.values());
        //
        loadEvent(scene);
        //
        Boolean redraw = eventAttributes.get("redraw") != null && eventAttributes.get("redraw").equals("true");
        if (redraw)
        {
            for (OBControl control : oldControls)
            {
                if (control == null) continue;
                detachControl(control);
                objectDict.remove(control);
            }
            //
            for (OBControl control : filterControls(".*"))
            {
                Map attributes = control.attributes();
                if (attributes != null)
                {
                    String fit = (String) attributes.get("fit");
                    if (fit != null && fit.equals("fitwidth"))
                    {
                        float scale = bounds().width() / control.width();
                        PointF position = OC_Generic.copyPoint(control.position());
                        float originalHeight = control.height();
                        control.setScale(scale * control.scale());
                        float heightDiff = control.height() - originalHeight;
                        position.y += heightDiff / 2;
                        control.setPosition(position);
                    }
                }
            }
        }
        //
        currentDemoAudioIndex = 0;
        //
        OC_Generic.colourObjectsWithScheme(this);
        //
        action_prepareScene(scene, redraw);
    }


    public void action_prepareScene (String scene, Boolean redraw)
    {
        currentTry = 1;
        trace_arrow = objectDict.get("trace_arrow");
        currentDemoAudioIndex = 0;
        //
        tracing_reset();
    }


    public void doAudio (String scene) throws Exception
    {
        setReplayAudioScene(currentEvent(), "REPEAT");
        playAudioQueuedScene(scene, "PROMPT", false);
    }


    public void doMainXX () throws Exception
    {
        playAudioQueuedScene(currentEvent(), "DEMO", true);
        doAudio(currentEvent());
        //
        tracing_reset();
        //
        setStatus(STATUS_WAITING_FOR_TRACE);
    }


    // TRACING

    public void tracing_reset ()
    {
        tracing_reset(null);
    }

    public void tracing_reset (final Integer number)
    {
        lastPointAdded = null;
        uPaths = null;
        if (subPaths != null)
        {
            for (OBControl c : subPaths)
                detachControl(c);
            subPaths = null;
        }
        //
        if (doneTraces != null)
        {
            for (OBControl c : doneTraces)
                detachControl(c);
        }
        //
        doneTraces = new ArrayList<>();
        if (currentTrace != null)
        {
            detachControl(currentTrace);
            currentTrace = null;
        }
        //
        finished = false;
        if (trace_arrow != null) trace_arrow.hide();
        //
        subPathIndex = 0;
        segmentIndex = 0;
        //
        new OBRunnableSyncUI()
        {
            public void ex ()
            {
                tracing_setup(number);
                tracing_position_arrow();
            }
        }.run();
    }


    public void tracing_setup ()
    {
        tracing_setup(null);
    }

    public void tracing_setup (final Integer number)
    {
//        MainActivity.log("tracing_setup: " + number);
        new OBRunnableSyncUI()
        {
            public void ex ()
            {
                String traceControl = (number == null) ? "trace" : String.format("trace_%d", number);
                path1 = (OBGroup) objectDict.get(traceControl);
                //
                String dashControl = (number == null) ? "dash" : String.format("dash_%d", number);
                dash1 = (OBImage) objectDict.get(dashControl);
                if (dash1 != null)
                {
                    dash1.show();
                }
                //
                if (path1 != null)
                {
                    uPaths = tracing_processDigit(path1); // only one number to trace
                    subPaths = new ArrayList();
                    subPaths.addAll(tracing_subpathControlsFromPath(traceControl));
                    path1.hide();
                    for (OBControl c : subPaths) c.hide();
                }
            }
        }.run();
    }


    public List<OBPath> tracing_processDigit (OBGroup digit)
    {
        List<OBPath> arr = new ArrayList<>();
        //
        if (digit != null)
        {
            for (int i = 1; i <= 2; i++)
            {
                OBPath p = (OBPath) digit.objectDict.get(String.format("p%d", i));
                if (p != null)
                {
                    arr.add(p);
                }
                else
                    break;
            }
        }
        return arr;
    }


    public OBGroup splitPath (OBPath obp)
    {
        int lengthPerSplit = 100;
        PathMeasure pm = new PathMeasure(obp.path(), false);
        float splen = pm.getLength();
        int noSplits = (int) (splen / lengthPerSplit);
        List<OBPath> newOBPaths = splitInto(obp, pm, noSplits, 1.0f / (noSplits * 4));
        for (OBPath newOBPath : newOBPaths)
        {
            //newOBPath.setBounds(obp.bounds);
            //newOBPath.setPosition(obp.position());
            //newOBPath.sizeToBox(obp.bounds());
            newOBPath.setStrokeColor(Color.argb((int) (255 * 0.4f), 255, 0, 0));
            newOBPath.setLineJoin(OBStroke.kCALineJoinRound);
            newOBPath.setFillColor(0);
            newOBPath.setLineWidth(swollenLineWidth);
        }
        MainActivity.log("Now serving " + newOBPaths.size());
        OBGroup grp = new OBGroup((List<OBControl>) (Object) newOBPaths);
        return grp;
    }


    public List<OBGroup> tracing_subpathControlsFromPath (String str)
    {
        List<OBGroup> arr = new ArrayList<>();
        OBGroup p = (OBGroup) objectDict.get(str);
        if (p != null)
        {
            for (int i = 1; i < 10; i++)
            {
                String pp = String.format("p%d", i);
                OBPath characterfragment = (OBPath) p.objectDict.get(pp);
                if (characterfragment != null)
                {
                    float savelw = characterfragment.lineWidth();
                    characterfragment.setLineWidth(swollenLineWidth);
                    characterfragment.sizeToBoundingBoxIncludingStroke();
                    characterfragment.setLineWidth(savelw);
                    characterfragment.setStrokeEnd(0.0f);
                    OBGroup g = splitPath(characterfragment);
                    g.setScale(p.scale());
                    g.setPosition(p.position());
                    g.setPosition(convertPointFromControl(characterfragment.position(), characterfragment.parent));
                    attachControl(g);
                    arr.add(g);
                }
                else
                {
                    break;
                }
            }
        }
        return arr;
    }


    public void tracing_nextSubpath ()
    {
        try
        {
            if (++subPathIndex >= subPaths.size())
            {
                saveStatusClearReplayAudioSetChecking();
                //
                action_answerIsCorrect();
            }
            else
            {
                if (currentTrace != null)
                {
                    lastPointAdded = null;
                    doneTraces.add(currentTrace);
                    currentTrace = null;
                    finished = false;
                    segmentIndex = 0;
                    positionArrow();
                }
                revertStatusAndReplayAudio();
                setStatus(STATUS_WAITING_FOR_TRACE);
            }
        }
        catch (Exception exception)
        {
        }
    }


    public void pointer_traceAlongPath (final OBPath p, float durationMultiplier) throws Exception
    {
        lockScreen();
        p.setStrokeColor(pathColour);
        p.setStrokeEnd(0.0f);
        p.setOpacity(1.0f);
        //
        p.parent.primogenitor().show();
        p.show();
        trace_arrow.hide();
        unlockScreen();
        //
        long starttime = SystemClock.uptimeMillis();
        float duration = p.length() * 2 * durationMultiplier / theMoveSpeed;
        float frac = 0;
        while (frac <= 1f)
        {
            long currtime = SystemClock.uptimeMillis();
            frac = (float) (currtime - starttime) / (duration * 1000);
            final float t = (frac);
            new OBRunnableSyncUI()
            {
                public void ex ()
                {
                    lockScreen();
                    p.setStrokeEnd(t);
                    thePointer.setPosition(convertPointFromControl(p.sAlongPath(t, null), p));
                    p.parent.primogenitor().setNeedsRetexture();
                    unlockScreen();
                }
            }.run();
            waitForSecs(0.02f);
        }
    }


    public PointF tracing_position_arrow ()
    {
        try
        {
            PointF outvec = new PointF();
            OBPath p = uPaths.get(subPathIndex);
            PointF arrowpoint = convertPointFromControl(p.sAlongPath(0.0f, outvec), p);
            trace_arrow.setPosition(arrowpoint);
            trace_arrow.rotation = (float) Math.atan2(outvec.x, -outvec.y);
            trace_arrow.show();
            trace_arrow.setZPosition(50);
            trace_arrow.setOpacity(1.0f);
            return arrowpoint;
        }
        catch (Exception e)
        {
            return null;
        }
    }


    public void action_playNextDemoSentence (Boolean waitAudio) throws Exception
    {
        playAudioQueuedSceneIndex(currentEvent(), "DEMO", currentDemoAudioIndex, waitAudio);
        currentDemoAudioIndex++;
    }


    public OBControl findTarget (PointF pt)
    {
        Boolean contained1 = dash1 != null && dash1.frame.contains(pt.x, pt.y);
        Boolean contained2 = dash2 != null && dash2.frame.contains(pt.x, pt.y);
        //
        if (!contained1 && !contained2)
        {
            return null;
        }
        OBControl c = finger(-1, 2, targets, pt);
        return c;
    }


    public void checkTraceStart (PointF pt)
    {
        saveStatusClearReplayAudioSetChecking();
        //
        boolean ok = condition_isPointInSegment(pt);
        if (!ok && currentTrace != null)
        {
            ok = pointInSegment(lastTracedPoint(), segmentIndex + 1) && pointInSegment(pt, segmentIndex + 1);
            if (ok)
                segmentIndex++;
        }
        if (ok)
        {
            if (trace_arrow != null)
            {
                trace_arrow.hide();
            }
            //
            if (currentTrace == null)
            {
                startNewSubpath();
                PointF cpt = convertPointToControl(pt, currentTrace);
                currentTrace.moveToPoint(cpt.x, cpt.y);
                currentTrace.addLineToPoint(cpt.x + 1, cpt.y + 1);
            }
            else
            {
                addPointToTrace(pt, false);
            }
            revertStatusAndReplayAudio();
            setStatus(STATUS_TRACING);
        }
        else
        {
            addPointToTrace(pt, true);
            revertStatusAndReplayAudio();
        }
    }


    public void addPointToTrace (PointF pt, boolean searchForSegmentIndex)
    {
        if (currentTrace != null)
        {
            if (searchForSegmentIndex)
            {
                for (int i = segmentIndex; i < subPaths.get(subPathIndex).members.size(); i++)
                {
                    if (pointInSegment(lastTracedPoint(), i))
                    {
                        PointF cpt = convertPointToControl(pt, currentTrace);
                        currentTrace.addLineToPoint(cpt.x, cpt.y);
                        segmentIndex = i;
                        return;
                    }
                }
            }
            else
            {
                PointF cpt = convertPointToControl(pt, currentTrace);
                currentTrace.addLineToPoint(cpt.x, cpt.y);
            }
        }
    }


    public Boolean condition_isPointInSegment (PointF pt)
    {
        if (uPaths == null || uPaths.size() == 0) return false;
        //
        if (subPaths == null || subPaths.size() == 0) return false;
        //
        return pointInSegment(pt, segmentIndex);
    }


    public void touchUpAtPoint (PointF pt, View v)
    {
        if (status() == STATUS_TRACING)
        {
            saveStatusClearReplayAudioSetChecking();
            //
            effectMoveToPoint(pt);
            if (finished)
            {
                OBUtils.runOnOtherThread(new OBUtils.RunLambda()
                {
                    @Override
                    public void run () throws Exception
                    {
                        tracing_nextSubpath();
                    }
                });
            }
            else
            {
                revertStatusAndReplayAudio();
                setStatus(STATUS_WAITING_FOR_TRACE);
            }
        }
    }


    public void effectMoveToPoint (PointF pt)
    {
        if (!pointHitRelativeSeg(pt, 0))
        {
            if (segmentIndex + 1 < subPaths.get(subPathIndex).members.size())
            {
                if (pointHitRelativeSeg(pt, +1))
                {
                    segmentIndex++;
                }
                else
                {
                    new AsyncTask<Void, Void, Void>()
                    {
                        protected Void doInBackground (Void... params)
                        {
                            try
                            {
                                gotItWrongWithSfx();
                                setStatus(STATUS_WAITING_FOR_TRACE);
                            }
                            catch (Exception exception)
                            {
                            }
                            return null;
                        }
                    }.execute();

                    return;
                }
            }
            else
            {
                finished = true;
                return;
            }
        }
        if (segmentIndex + 1 == subPaths.get(subPathIndex).members.size())
            finished = true;
        //
        addPointToTrace(pt, false);
    }


    public void touchMovedToPoint (PointF pt, View v)
    {
        if (status() == STATUS_TRACING)
        {
            effectMoveToPoint(pt);
        }
    }


    public Boolean performTouchDown (PointF pt)
    {
        try
        {
            Method m = this.getClass().getMethod("action_touchDown", PointF.class);
            m.invoke(this, pt);
            return true;
        }
        catch (Exception e)
        {
            // ignore the exception
        }
        return false;
    }


    public void action_cleanupTracing ()
    {
        lockScreen();
        tracing_reset();
        tracing_position_arrow();
        unlockScreen();
        //
        setStatus(STATUS_WAITING_FOR_TRACE);
    }


    public void touchDownAtPoint (PointF pt, View v)
    {
        if (status() == STATUS_AWAITING_CLICK)
        {
            if (!performTouchDown(pt))
            {
                List<OBControl> controls = new ArrayList();
                if (dash1 != null) controls.add(dash1);
                if (dash2 != null) controls.add(dash2);
                OBControl closestTarget = finger(-1, 2, controls, pt);
                if (closestTarget != null)
                {
                    OBUtils.runOnOtherThread(new OBUtils.RunLambda()
                    {
                        @Override
                        public void run () throws Exception
                        {
                            action_cleanupTracing();
                        }
                    });
                }
            }
        }
        else if (status() == STATUS_WAITING_FOR_TRACE)
        {
            checkTraceStart(pt);
        }
    }


    public String action_getScenesProperty ()
    {
        return "scenes";
    }


    // Miscelaneous Functions
    public void playSceneAudio (String scene, Boolean wait) throws Exception
    {
        playAudioQueuedScene(currentEvent(), scene, wait);
        if (!wait) waitForSecs(0.01);
    }

    public void playSceneAudioIndex (String scene, int index, Boolean wait) throws Exception
    {
        playAudioQueuedSceneIndex(currentEvent(), scene, index, wait);
        if (!wait) waitForSecs(0.01);
    }


    public OBLabel action_createLabelForControl (OBControl control)
    {
        return action_createLabelForControl(control, 1.0f, false);
    }


    public OBLabel action_createLabelForControl (OBControl control, float finalResizeFactor)
    {
        return action_createLabelForControl(control, finalResizeFactor, true);
    }


    public OBLabel action_createLabelForControl (OBControl control, float finalResizeFactor, Boolean insertIntoGroup)
    {
        return OC_Generic.action_createLabelForControl(control, finalResizeFactor, insertIntoGroup, this);
    }


    // CHECKING functions

    public void saveStatusClearReplayAudioSetChecking ()
    {
        savedStatus = status();
        setStatus(STATUS_CHECKING);
        //
        if (_replayAudio != null && _replayAudio.size() > 0)
        {
            savedReplayAudio = _replayAudio;
        }
        setReplayAudio(null);
    }

    public void revertStatusAndReplayAudio ()
    {
        setStatus(savedStatus);
        setReplayAudio(savedReplayAudio);
    }

}