/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.classifier;

import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;

import static com.google.common.truth.Truth.assertThat;

import android.testing.AndroidTestingRunner;

import androidx.test.filters.SmallTest;

import com.android.systemui.util.DeviceConfigProxyFake;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Random;

@SmallTest
@RunWith(AndroidTestingRunner.class)
public class ZigZagClassifierTest extends ClassifierTest {

    private FalsingClassifier mClassifier;

    @Before
    public void setup() {
        super.setup();
        mClassifier = new ZigZagClassifier(getDataProvider(), new DeviceConfigProxyFake());
    }

    @After
    public void tearDown() {
        super.tearDown();
    }

    @Test
    public void testPass_fewTouchesVertical() {
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
        appendMoveEvent(0, 0);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
        appendMoveEvent(0, 100);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testPass_vertical() {
        appendMoveEvent(0, 0);
        appendMoveEvent(0, 100);
        appendMoveEvent(0, 200);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testPass_dropClosingUpEvent() {
        appendMoveEvent(0, 0);
        appendMoveEvent(0, 100);
        appendMoveEvent(0, 200);
        appendUpEvent(0, 180); // this event would push us over the maxDevianceY
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testPass_fewTouchesHorizontal() {
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
        appendMoveEvent(0, 0);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
        appendMoveEvent(100, 0);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testPass_horizontal() {
        appendMoveEvent(0, 0);
        appendMoveEvent(100, 0);
        appendMoveEvent(200, 0);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testPass_brightnessSliderAlwaysPasses() {
        appendMoveEvent(0, 0);
        appendMoveEvent(0, 100);
        appendMoveEvent(0, 1);
        assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testFail_minimumTouchesVertical() {
        appendMoveEvent(0, 0);
        appendMoveEvent(0, 100);
        appendMoveEvent(0, 1);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void testFail_minimumTouchesHorizontal() {
        appendMoveEvent(0, 0);
        appendMoveEvent(100, 0);
        appendMoveEvent(1, 0);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void testPass_fortyFiveDegreesStraight() {
        appendMoveEvent(0, 0);
        appendMoveEvent(10, 10);
        appendMoveEvent(20, 20);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testPass_horizontalZigZagVerticalStraight() {
        // This test looks just like testFail_horizontalZigZagVerticalStraight but with
        // a longer y range, making it look straighter.
        appendMoveEvent(0, 0);
        appendMoveEvent(5, 100);
        appendMoveEvent(-5, 200);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testPass_horizontalStraightVerticalZigZag() {
        // This test looks just like testFail_horizontalStraightVerticalZigZag but with
        // a longer x range, making it look straighter.
        appendMoveEvent(0, 0);
        appendMoveEvent(100, 5);
        appendMoveEvent(200, -5);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
    }

    @Test
    public void testFail_horizontalZigZagVerticalStraight() {
        // This test looks just like testPass_horizontalZigZagVerticalStraight but with
        // a shorter y range, making it look more crooked.
        appendMoveEvent(0, 0);
        appendMoveEvent(6, 10);
        appendMoveEvent(-6, 20);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void testFail_horizontalStraightVerticalZigZag() {
        // This test looks just like testPass_horizontalStraightVerticalZigZag but with
        // a shorter x range, making it look more crooked.
        appendMoveEvent(0, 0);
        appendMoveEvent(10, 5);
        appendMoveEvent(20, -5);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between0And45() {
        appendMoveEvent(0, 0);
        appendMoveEvent(100, 5);
        appendMoveEvent(200, 10);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(100, 0);
        appendMoveEvent(200, 10);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(100, -10);
        appendMoveEvent(200, 10);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(100, -10);
        appendMoveEvent(200, 50);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between45And90() {
        appendMoveEvent(0, 0);
        appendMoveEvent(10, 50);
        appendMoveEvent(8, 100);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(1, 800);
        appendMoveEvent(2, 900);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-10, 600);
        appendMoveEvent(30, 700);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(40, 100);
        appendMoveEvent(0, 101);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between90And135() {
        appendMoveEvent(0, 0);
        appendMoveEvent(-10, 50);
        appendMoveEvent(-24, 100);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-20, 800);
        appendMoveEvent(-20, 900);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(30, 600);
        appendMoveEvent(-10, 700);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-80, 100);
        appendMoveEvent(-10, 101);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between135And180() {
        appendMoveEvent(0, 0);
        appendMoveEvent(-120, 10);
        appendMoveEvent(-200, 20);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-20, 8);
        appendMoveEvent(-40, 2);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-500, -2);
        appendMoveEvent(-600, 70);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-80, 100);
        appendMoveEvent(-100, 1);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between180And225() {
        appendMoveEvent(0, 0);
        appendMoveEvent(-120, -10);
        appendMoveEvent(-200, -20);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-20, -8);
        appendMoveEvent(-40, -2);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-500, 2);
        appendMoveEvent(-600, -70);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-80, -100);
        appendMoveEvent(-100, -1);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between225And270() {
        appendMoveEvent(0, 0);
        appendMoveEvent(-12, -20);
        appendMoveEvent(-20, -40);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-20, -130);
        appendMoveEvent(-40, -260);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(1, -100);
        appendMoveEvent(-6, -200);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-80, -100);
        appendMoveEvent(-10, -110);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between270And315() {
        appendMoveEvent(0, 0);
        appendMoveEvent(12, -20);
        appendMoveEvent(20, -40);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(20, -130);
        appendMoveEvent(40, -260);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(-1, -100);
        appendMoveEvent(6, -200);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(80, -100);
        appendMoveEvent(10, -110);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_between315And360() {
        appendMoveEvent(0, 0);
        appendMoveEvent(120, -20);
        appendMoveEvent(200, -40);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(200, -13);
        appendMoveEvent(400, -30);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(100, 10);
        appendMoveEvent(600, -20);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();

        resetDataProvider();
        appendMoveEvent(0, 0);
        appendMoveEvent(80, -100);
        appendMoveEvent(100, -1);
        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
    }

    @Test
    public void test_randomOrigins() {
        // The purpose of this test is to try all the other tests from different starting points.
        // We use a pre-determined seed to make this test repeatable.
        Random rand = new Random(23);
        for (int i = 0; i < 100; i++) {
            setOffsetX(rand.nextInt(2000) - 1000);
            setOffsetY(rand.nextInt(2000) - 1000);
            try {
                resetDataProvider();
                testPass_fewTouchesVertical();
                resetDataProvider();
                testPass_vertical();
                resetDataProvider();
                testFail_horizontalStraightVerticalZigZag();
                resetDataProvider();
                testFail_horizontalZigZagVerticalStraight();
                resetDataProvider();
                testFail_minimumTouchesHorizontal();
                resetDataProvider();
                testFail_minimumTouchesVertical();
                resetDataProvider();
                testPass_fewTouchesHorizontal();
                resetDataProvider();
                testPass_fortyFiveDegreesStraight();
                resetDataProvider();
                testPass_horizontal();
                resetDataProvider();
                testPass_horizontalStraightVerticalZigZag();
                resetDataProvider();
                testPass_horizontalZigZagVerticalStraight();
                resetDataProvider();
                test_between0And45();
                resetDataProvider();
                test_between45And90();
                resetDataProvider();
                test_between90And135();
                resetDataProvider();
                test_between135And180();
                resetDataProvider();
                test_between180And225();
                resetDataProvider();
                test_between225And270();
                resetDataProvider();
                test_between270And315();
                resetDataProvider();
                test_between315And360();
            } catch (AssertionError e) {
                throw new AssertionError("Random origin failure in iteration " + i, e);
            }
        }
    }
}
