/*
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * 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 io.reactivex.rxjava3.validators;

import java.io.*;
import java.util.*;
import java.util.regex.Pattern;

import org.junit.Test;

import io.reactivex.rxjava3.testsupport.TestHelper;

/**
 * Checks for commonly copy-pasted but not-renamed local variables in unit tests.
 * <ul>
 * <li>{@code TestSubscriber} named as {@code to*}</li>
 * <li>{@code TestObserver} named as {@code ts*}</li>
 * <li>{@code PublishProcessor} named as {@code ps*}</li>
 * <li>{@code PublishSubject} named as {@code pp*}</li>
 * <li>{@code Subscription} with single letter name such as "s" or "d"</li>
 * <li>{@code Disposable} with single letter name such as "s" or "d"</li>
 * <li>{@code Flowable} named as {@code o|observable} + number</li>
 * <li>{@code Observable} named as {@code f|flowable} + number</li>
 * <li>{@code Subscriber} named as "o" or "observer"</li>
 * <li>{@code Observer} named as "s" or "subscriber"</li>
 * </ul>
 */
public class CheckLocalVariablesInTests {

    static void findPattern(String pattern) throws Exception {
        findPattern(pattern, false);
    }

    static void findPattern(String pattern, boolean checkMain) throws Exception {
        File f = TestHelper.findSource("Flowable");
        if (f == null) {
            System.out.println("Unable to find sources of RxJava");
            return;
        }

        Queue<File> dirs = new ArrayDeque<>();

        StringBuilder fail = new StringBuilder();
        fail.append("The following code pattern was found: ").append(pattern).append("\n");

        File parent = f.getParentFile().getParentFile();

        if (checkMain) {
            dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/')));
        }
        dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/').replace("src/main/java", "src/test/java")));

        Pattern p = Pattern.compile(pattern);

        int total = 0;

        while (!dirs.isEmpty()) {
            f = dirs.poll();

            File[] list = f.listFiles();
            if (list != null && list.length != 0) {

                for (File u : list) {
                    if (u.isDirectory()) {
                        dirs.offer(u);
                    } else {
                        String fname = u.getName();
                        if (fname.endsWith(".java")) {

                            int lineNum = 0;
                            BufferedReader in = new BufferedReader(new FileReader(u));
                            try {
                                for (;;) {
                                    String line = in.readLine();
                                    if (line != null) {
                                        lineNum++;

                                        line = line.trim();

                                        if (!line.startsWith("//") && !line.startsWith("*")) {
                                            if (p.matcher(line).find()) {
                                                fail
                                                .append(fname)
                                                .append("#L").append(lineNum)
                                                .append("    ").append(line)
                                                .append("\n")
                                                .append(" at ")
                                                .append(fname.replace(".java", ""))
                                                .append(".method(")
                                                .append(fname)
                                                .append(":")
                                                .append(lineNum)
                                                .append(")\n");

                                                total++;
                                            }
                                        }
                                    } else {
                                        break;
                                    }
                                }
                            } finally {
                                in.close();
                            }
                        }
                    }
                }
            }
        }
        if (total != 0) {
            fail.insert(0, "Found " + total + " instances");
            System.out.println(fail);
            throw new AssertionError(fail.toString());
        }
    }

    @Test
    public void subscriberAsTo() throws Exception {
        findPattern("TestSubscriber(Ex)?<.*>\\s+to");
    }

    @Test
    public void observerAsTs() throws Exception {
        findPattern("TestObserver(Ex)?<.*>\\s+ts");
    }

    @Test
    public void subscriberNoArgAsTo() throws Exception {
        findPattern("TestSubscriber(Ex)?\\s+to");
    }

    @Test
    public void observerNoArgAsTs() throws Exception {
        findPattern("TestObserver(Ex)?\\s+ts");
    }

    @Test
    public void publishSubjectAsPp() throws Exception {
        findPattern("PublishSubject<.*>\\s+pp");
    }

    @Test
    public void publishProcessorAsPs() throws Exception {
        findPattern("PublishProcessor<.*>\\s+ps");
    }

    @Test
    public void unicastSubjectAsUp() throws Exception {
        findPattern("UnicastSubject<.*>\\s+up");
    }

    @Test
    public void unicastProcessorAsUs() throws Exception {
        findPattern("UnicastProcessor<.*>\\s+us");
    }

    @Test
    public void behaviorProcessorAsBs() throws Exception {
        findPattern("BehaviorProcessor<.*>\\s+bs");
    }

    @Test
    public void behaviorSubjectAsBp() throws Exception {
        findPattern("BehaviorSubject<.*>\\s+bp");
    }

    @Test
    public void connectableFlowableAsCo() throws Exception {
        findPattern("ConnectableFlowable<.*>\\s+co(0-9|\\b)");
    }

    @Test
    public void connectableObservableAsCf() throws Exception {
        findPattern("ConnectableObservable<.*>\\s+cf(0-9|\\b)");
    }

    @Test
    public void queueDisposableInsteadOfQueueFuseable() throws Exception {
        findPattern("QueueDisposable\\.(NONE|SYNC|ASYNC|ANY|BOUNDARY)");
    }

    @Test
    public void queueSubscriptionInsteadOfQueueFuseable() throws Exception {
        findPattern("QueueSubscription\\.(NONE|SYNC|ASYNC|ANY|BOUNDARY)");
    }

    @Test
    public void singleSourceAsMs() throws Exception {
        findPattern("SingleSource<.*>\\s+ms");
    }

    @Test
    public void singleSourceAsCs() throws Exception {
        findPattern("SingleSource<.*>\\s+cs");
    }

    @Test
    public void maybeSourceAsSs() throws Exception {
        findPattern("MaybeSource<.*>\\s+ss");
    }

    @Test
    public void maybeSourceAsCs() throws Exception {
        findPattern("MaybeSource<.*>\\s+cs");
    }

    @Test
    public void completableSourceAsSs() throws Exception {
        findPattern("CompletableSource<.*>\\s+ss");
    }

    @Test
    public void completableSourceAsMs() throws Exception {
        findPattern("CompletableSource<.*>\\s+ms");
    }

    @Test
    public void observableAsC() throws Exception {
        findPattern("Observable<.*>\\s+c\\b");
    }

    @Test
    public void subscriberAsObserver() throws Exception {
        findPattern("Subscriber<.*>\\s+observer[0-9]?\\b");
    }

    @Test
    public void subscriberAsO() throws Exception {
        findPattern("Subscriber<.*>\\s+o[0-9]?\\b");
    }

    @Test
    public void singleAsObservable() throws Exception {
        findPattern("Single<.*>\\s+observable\\b");
    }

    @Test
    public void singleAsFlowable() throws Exception {
        findPattern("Single<.*>\\s+flowable\\b");
    }

    @Test
    public void observerAsSubscriber() throws Exception {
        findPattern("Observer<.*>\\s+subscriber[0-9]?\\b");
    }

    @Test
    public void observerAsS() throws Exception {
        findPattern("Observer<.*>\\s+s[0-9]?\\b");
    }

    @Test
    public void observerNoArgAsSubscriber() throws Exception {
        findPattern("Observer\\s+subscriber[0-9]?\\b");
    }

    @Test
    public void observerNoArgAsS() throws Exception {
        findPattern("Observer\\s+s[0-9]?\\b");
    }

    @Test
    public void flowableAsObservable() throws Exception {
        findPattern("Flowable<.*>\\s+observable[0-9]?\\b");
    }

    @Test
    public void flowableAsO() throws Exception {
        findPattern("Flowable<.*>\\s+o[0-9]?\\b");
    }

    @Test
    public void flowableNoArgAsO() throws Exception {
        findPattern("Flowable\\s+o[0-9]?\\b");
    }

    @Test
    public void flowableNoArgAsObservable() throws Exception {
        findPattern("Flowable\\s+observable[0-9]?\\b");
    }

    @Test
    public void processorAsSubject() throws Exception {
        findPattern("Processor<.*>\\s+subject(0-9)?\\b");
    }

    @Test
    public void maybeAsObservable() throws Exception {
        findPattern("Maybe<.*>\\s+observable\\b");
    }

    @Test
    public void maybeAsFlowable() throws Exception {
        findPattern("Maybe<.*>\\s+flowable\\b");
    }

    @Test
    public void completableAsObservable() throws Exception {
        findPattern("Completable\\s+observable\\b");
    }

    @Test
    public void completableAsFlowable() throws Exception {
        findPattern("Completable\\s+flowable\\b");
    }

    @Test
    public void subscriptionAsFieldS() throws Exception {
        findPattern("Subscription\\s+s[0-9]?;", true);
    }

    @Test
    public void subscriptionAsD() throws Exception {
        findPattern("Subscription\\s+d[0-9]?", true);
    }

    @Test
    public void subscriptionAsSubscription() throws Exception {
        findPattern("Subscription\\s+subscription[0-9]?;", true);
    }

    @Test
    public void subscriptionAsDParenthesis() throws Exception {
        findPattern("Subscription\\s+d[0-9]?\\)", true);
    }

    @Test
    public void queueSubscriptionAsD() throws Exception {
        findPattern("Subscription<.*>\\s+q?d[0-9]?\\b", true);
    }

    @Test
    public void booleanSubscriptionAsbd() throws Exception {
        findPattern("BooleanSubscription\\s+bd[0-9]?;", true);
    }

    @Test
    public void atomicSubscriptionAsS() throws Exception {
        findPattern("AtomicReference<Subscription>\\s+s[0-9]?;", true);
    }

    @Test
    public void atomicSubscriptionAsSInit() throws Exception {
        findPattern("AtomicReference<Subscription>\\s+s[0-9]?\\s", true);
    }

    @Test
    public void atomicSubscriptionAsSubscription() throws Exception {
        findPattern("AtomicReference<Subscription>\\s+subscription[0-9]?", true);
    }

    @Test
    public void atomicSubscriptionAsD() throws Exception {
        findPattern("AtomicReference<Subscription>\\s+d[0-9]?", true);
    }

    @Test
    public void disposableAsS() throws Exception {
        // the space before makes sure it doesn't match onSubscribe(Subscription) unnecessarily
        findPattern("Disposable\\s+s[0-9]?\\b", true);
    }

    @Test
    public void disposableAsFieldD() throws Exception {
        findPattern("Disposable\\s+d[0-9]?;", true);
    }

    @Test
    public void atomicDisposableAsS() throws Exception {
        findPattern("AtomicReference<Disposable>\\s+s[0-9]?", true);
    }

    @Test
    public void atomicDisposableAsD() throws Exception {
        findPattern("AtomicReference<Disposable>\\s+d[0-9]?;", true);
    }

    @Test
    public void subscriberAsFieldActual() throws Exception {
        findPattern("Subscriber<.*>\\s+actual[;\\)]", true);
    }

    @Test
    public void subscriberNoArgAsFieldActual() throws Exception {
        findPattern("Subscriber\\s+actual[;\\)]", true);
    }

    @Test
    public void subscriberAsFieldS() throws Exception {
        findPattern("Subscriber<.*>\\s+s[0-9]?;", true);
    }

    @Test
    public void observerAsFieldActual() throws Exception {
        findPattern("Observer<.*>\\s+actual[;\\)]", true);
    }

    @Test
    public void observerAsFieldSO() throws Exception {
        findPattern("Observer<.*>\\s+[so][0-9]?;", true);
    }

    @Test
    public void observerNoArgAsFieldActual() throws Exception {
        findPattern("Observer\\s+actual[;\\)]", true);
    }

    @Test
    public void observerNoArgAsFieldCs() throws Exception {
        findPattern("Observer\\s+cs[;\\)]", true);
    }

    @Test
    public void observerNoArgAsFieldSO() throws Exception {
        findPattern("Observer\\s+[so][0-9]?;", true);
    }

    @Test
    public void queueDisposableAsD() throws Exception {
        findPattern("Disposable<.*>\\s+q?s[0-9]?\\b", true);
    }

    @Test
    public void disposableAsDParenthesis() throws Exception {
        findPattern("Disposable\\s+s[0-9]?\\)", true);
    }

    @Test
    public void compositeDisposableAsCs() throws Exception {
        findPattern("CompositeDisposable\\s+cs[0-9]?", true);
    }

}
