package de.duehl.basics.io;

/*
 * Copyright 2021 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Diese Klasse durchsucht Dateien mit einer bestimmten Endung ab einem bestimmten Verzeichnis.
 *
 * @version 1.01     2021-04-23
 * @author Christian Dühl
 */

public class FindInSpecialFiles {

    /** Filter für die gewünschten Dateien. */
    private final FilenameFilter filter;

    /** Filter für Unterverzeichnisse. */
    private final FilenameFilter directoryFilter;

    /**
     * Konstruktor.
     *
     * @param fileExtension
     *            Endung der gewünschten Dateiart, etwa ".java" oder ".txt".
     */
    public FindInSpecialFiles(String fileExtension) {
        filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(fileExtension);
            }
        };
        directoryFilter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                File test = new File(dir.getPath() + File.separator + name);
                return test.isDirectory();
            }
        };
    }

    /**
     * Durchsucht alle passenden Dateien unterhalb des Ausgangsverzeichnisses nach dem Suchtext.
     *
     * @param directory
     *            Ausgangsverzeichnis
     * @param search
     *            Suchtext
     * @return Liste mit den Fundstellen
     */
    public List<FileSearchResult> find(String directory, String search) {
        List<FileSearchResult> results = new ArrayList<>();

        File dir = new File(directory);
        for (String file : dir.list(filter)) {
            String fullFile = dir.getPath() + File.separator + file;
            results.addAll(findInOneFile(fullFile, search));
        }

        for (String subdir : dir.list(directoryFilter)) {
            String fullSubDir = dir.getPath() + File.separator + subdir;
            results.addAll(find(fullSubDir, search));
        }

        return results;
    }

    /**
     * Durchsucht eine Datei nach Fundstellen.
     *
     * @param file
     *            Datei
     * @param search
     *            Suchtext
     * @return Liste mit den Fundstellen.
     */
    private List<FileSearchResult> findInOneFile(String file, String search) {
        List<FileSearchResult> results = new ArrayList<>();

        FineFileReader reader = new FineFileReader(file);

        /*
         * Bei der Verwendung von reader.readFileToListOfStrings() wird dann leider immer
         * als Zeilennummer die letzte Zeie ausgegeben. Daher muss man es zu Fuß machen:
         */

        String line = reader.readNextLine();
        while (line != null) {
            if (line.contains(search)) {
                FileSearchResult result = new FileSearchResult(file, reader.getLineNumber(), line);
                results.add(result);
            }
            line = reader.readNextLine();
        }

        reader.close();

        return results;
    }

    /**
     * Durchsucht alle passenden Dateien unterhalb des Ausgangsverzeichnisses nach dem Suchtext.
     *
     * @param directory
     *            Ausgangsverzeichnis
     * @param searchPattern
     *            Pattern für die Suche.
     * @return Liste mit den Fundstellen
     */
    public List<FilePatternSearchResult> findWithPattern(String directory, Pattern searchPattern) {
        List<FilePatternSearchResult> results = new ArrayList<>();

        File dir = new File(directory);
        for (String file : dir.list(filter)) {
            String fullFile = dir.getPath() + File.separator + file;
            results.addAll(findWithPatternInOneFile(fullFile, searchPattern));
        }

        for (String subdir : dir.list(directoryFilter)) {
            String fullSubDir = dir.getPath() + File.separator + subdir;
            results.addAll(findWithPattern(fullSubDir, searchPattern));
        }

        return results;
    }

    /**
     * Durchsucht eine Datei nach Fundstellen.
     *
     * @param file
     *            Datei
     * @param searchPattern
     *            Pattern für die Suche.
     * @return Liste mit den Fundstellen.
     */
    private List<FilePatternSearchResult> findWithPatternInOneFile(String file,
            Pattern searchPattern) {
        List<FilePatternSearchResult> results = new ArrayList<>();

        FineFileReader reader = new FineFileReader(file);

        /*
         * Bei der Verwendung von reader.readFileToListOfStrings() wird dann leider immer
         * als Zeilennummer die letzte Zeie ausgegeben. Daher muss man es zu Fuß machen:
         */

        String line = reader.readNextLine();
        while (line != null) {
            Matcher matcher = searchPattern.matcher(line);
            while (matcher.find()) {
                FilePatternSearchResult result = new FilePatternSearchResult(
                        file, reader.getLineNumber(), line, matcher.group(1));
                results.add(result);
            }
            line = reader.readNextLine();
        }

        reader.close();

        return results;
    }

}
