/*
 * Copyright 2021, TeamDev. All rights reserved.
 *
 * 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
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.tools.dart.fs;

import com.google.common.flogger.FluentLogger;
import com.google.errorprone.annotations.Immutable;
import io.spine.logging.Logging;
import io.spine.tools.code.Element;
import io.spine.tools.fs.ExternalModule;
import io.spine.tools.fs.ExternalModules;
import io.spine.tools.fs.FileReference;
import org.checkerframework.checker.regex.qual.Regex;

import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import static java.util.regex.Pattern.compile;

/**
 * A source code line with an import statement.
 */
@Immutable
final class ImportStatement implements Element, Logging {

    @Regex(2)
    private static final Pattern PATTERN = compile("import [\"']([^:]+)[\"'] as (.+);");

    private final Path sourceDirectory;
    private final String text;

    /**
     * Creates a new instance with the passed value.
     *
     * @param file
     *         the file declaring the line
     * @param text
     *         the text of the source code line with the import statement
     */
    ImportStatement(DartFile file, String text) {
        this(file.directory(), text);
    }

    private ImportStatement(Path sourceDirectory, String text) {
        this.sourceDirectory = sourceDirectory;
        this.text = checkNotNull(text);
        Matcher matcher = PATTERN.matcher(text);
        checkArgument(
                matcher.matches(),
                "The passed text is not recognized as an import statement (`%s`).", text
        );
    }

    /**
     * Tells if the passed text is an import statement.
     */
    static boolean isDeclaredIn(String text) {
        checkNotNull(text);
        Matcher matcher = PATTERN.matcher(text);
        return matcher.matches();
    }

    /**
     * If this line contains an import statement, converts it to a relative import statement
     * wherever possible.
     *
     * <p>If this line is not an import statement, returns it as is.
     *
     * <p>For import statements, converts an absolute path found after
     * {@code import} clause into a relative path, if one of the passed modules contains
     * such a file.
     *
     * <p>If none of the modules have such a file, returns the source code line as is.
     *
     * @param libPath
     *         the path to the {@code lib} project folder
     * @param modules
     *         the modules of the project to check the file referenced in
     *         the {@code import} statement
     */
    public ImportStatement resolve(Path libPath, ExternalModules modules) {
        Path relativePath = importRelativeTo(libPath);
        FileReference reference = FileReference.of(relativePath);
        for (ExternalModule module : modules.asList()) {
            if (module.provides(reference)) {
                return resolve(module, relativePath);
            }
        }
        return this;
    }

    /**
     * Transforms the path found in the import statement to a path relative
     * to the file of this source line.
     */
    private Path importRelativeTo(Path libPath) {
        FluentLogger.Api debug = _debug();
        debug.log("Import statement found in line: `%s`.", text);
        String path = matcher().group(1);
        Path absolutePath = sourceDirectory.resolve(path).normalize();
        debug.log("Resolved against this file: `%s`.", absolutePath);
        Path relativePath = libPath.relativize(absolutePath);
        debug.log("Relative path: `%s`.", relativePath);
        return relativePath;
    }

    private ImportStatement resolve(ExternalModule module, Path relativePath) {
        String resolved = format(
                "import 'package:%s/%s' as %s;",
                module.name(), relativePath, matcher().group(2)
        );
        _debug().log("Replacing with `%s`.", resolved);
        return new ImportStatement(sourceDirectory, resolved);
    }

    private Matcher matcher() {
        return PATTERN.matcher(text);
    }

    @Override
    public String text() {
        return text;
    }

    @Override
    public String toString() {
        return text;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof ImportStatement)) {
            return false;
        }
        ImportStatement other = (ImportStatement) o;
        return text.equals(other.text) && sourceDirectory.equals(other.sourceDirectory);
    }

    @Override
    public int hashCode() {
        return text.hashCode();
    }
}

@@ -2225,16 +2225,101 @@
 ng text;
+%0A    private final String importPathAsDeclared;%0A    private final String importAlias;
 %0A%0A    /*
@@ -2394,17 +2394,23 @@
  @param 
-f
+sourceF
 ile%0A    
@@ -2433,22 +2433,23 @@
 ile 
-declar
+contain
 ing the 
 line
@@ -2444,16 +2444,21 @@
 ing the 
+code 
 line%0A   
@@ -2584,17 +2584,23 @@
 artFile 
-f
+sourceF
 ile, Str
@@ -2624,17 +2624,23 @@
    this(
-f
+sourceF
 ile.dire
@@ -2744,81 +2744,81 @@
 his.
-sourceDirectory = sourceDirectory;%0A        this.text = checkNotNull(text)
+text = checkNotNull(text);%0A        this.sourceDirectory = sourceDirectory
 ;%0A  
@@ -3018,24 +3018,123 @@
 %0A        );%0A
+        this.importPathAsDeclared = matcher.group(1);%0A        this.importAlias = matcher.group(2);%0A
     %7D%0A%0A    /
@@ -4785,50 +4785,8 @@
 t);%0A
-        String path = matcher().group(1);%0A
     
@@ -4833,20 +4833,36 @@
 resolve(
-path
+importPathAsDeclared
 ).normal
@@ -5305,26 +5305,19 @@
 th, 
-matcher().group(2)
+importAlias
 %0A   
@@ -5454,85 +5454,8 @@
  %7D%0A%0A
-    private Matcher matcher() %7B%0A        return PATTERN.matcher(text);%0A    %7D%0A%0A
     

Remove extra regex operations