/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 org.jboss.installer.common;

import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.FileUtils;
import org.jboss.installer.core.MnemonicUtils;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.io.File;
import java.nio.file.Path;

import static org.jboss.installer.screens.DefaultScreen.FIELD_ROW_INSET;
import static org.jboss.installer.screens.DefaultScreen.RIGHT_INDENT_INSET;

public class FileChooserPanel extends JPanel {

    private final JTextField textField;
    private final FileNameExtensionFilter fileFilter;
    private final JButton browseButton;
    private final boolean showHidden;

    // TODO: change baseFolder to Path
    private FileChooserPanel(String buttonText, String baseFolder, int fileSelectionMode,
                             FileNameExtensionFilter fileFilter, MnemonicUtils mnemonicUtils,
                             boolean showHidden) {
        this.fileFilter = fileFilter;
        this.showHidden = showHidden;

        this.setLayout(new GridBagLayout());
        final GridBagConstraints cc = UiResources.initializeConstraints();
        cc.insets = RIGHT_INDENT_INSET;
        cc.weightx = 0.8;
        textField = UiResources.createTextField(baseFolder);
        textField.setColumns(10);
        this.add(textField, cc);
        cc.gridx = 1;
        cc.weightx = 0;
        cc.insets = FIELD_ROW_INSET;
        browseButton = UiResources.createButton(buttonText, actionEvent -> displayFileSelector(fileSelectionMode, baseFolder));
        if (mnemonicUtils != null) {
            mnemonicUtils.findMnemonic(buttonText).ifPresent(browseButton::setMnemonic);
        }
        this.add(browseButton, cc);
    }

    public static Builder builder(LanguageUtils utils, int fileSelectionMode, MnemonicUtils mnemonicUtils) {
        return new Builder(utils, fileSelectionMode, mnemonicUtils);
    }

    public static class Builder {
        private final LanguageUtils utils;
        private static final String BROWSE_KEY = "generic.browse";
        private final int fileSelectionMode;
        private final MnemonicUtils mnemonicUtils;
        private String buttonText;
        private String baseFolder = System.getProperty("user.home");
        private FileNameExtensionFilter fileFilter = null;
        private boolean showHidden = false;

        private Builder(LanguageUtils utils, int fileSelectionMode, MnemonicUtils mnemonicUtils) {
            this.utils = utils;
            this.buttonText = utils.getString(BROWSE_KEY);
            this.fileSelectionMode = fileSelectionMode;
            this.mnemonicUtils = mnemonicUtils;
        }

        /**
         * Sets the content of the button directly to what is passed in {@code buttonText}
         * @param buttonText what will be directly displayed
         */
        public Builder rawButtonText(String buttonText) {
            this.buttonText = buttonText;
            return this;
        }

        /**
         * Finds the {@code bundleKey} inside {@code LanguageUtils} and sets localized text for button
         * @param bundleKey key for searching in bundled keys
         */
        public Builder buttonText(String bundleKey) {
            this.buttonText = utils.getString(bundleKey);
            return this;
        }

        public Builder baseFolder(String baseFolder) {
            this.baseFolder = baseFolder;
            return this;
        }

        public Builder fileFilter(FileNameExtensionFilter fileFilter) {
            this.fileFilter = fileFilter;
            return this;
        }

        public Builder showHidden(boolean showHidden) {
            this.showHidden = showHidden;
            return this;
        }

        public FileChooserPanel build() {
            return new FileChooserPanel(buttonText, baseFolder, fileSelectionMode,
                    fileFilter, mnemonicUtils, showHidden);
        }
    }

    public String getText() {
        return textField.getText().trim().isEmpty() ? null : textField.getText().trim();
    }

    public String asPath() throws InvalidPathException {
        try {
            return getText()==null?null:Path.of(getText()).toAbsolutePath().toString();
        } catch (java.nio.file.InvalidPathException e) {
            throw new InvalidPathException("Cannot convert to Path", getText(), e);
        }
    }

    public String asPathOrUrl() throws InvalidPathException {
        final String trimmedText = getText();
        if (trimmedText == null) {
            return null;
        }

        if (FileUtils.isUrl(trimmedText)) {
            return trimmedText;
        } else {
            try {
                return Path.of(trimmedText).toAbsolutePath().toString();
            } catch (java.nio.file.InvalidPathException e) {
                throw new InvalidPathException("Cannot convert to Path", getText(), e);
            }
        }
    }

    public void setEnabled(boolean flag) {
        textField.setEnabled(flag);
        browseButton.setEnabled(flag);
    }

    public void setVisible(boolean flag) {
        textField.setVisible(flag);
        browseButton.setVisible(flag);
    }

    @Override
    public boolean isVisible() {
        return textField.isVisible() || browseButton.isVisible();
    }

    @Override
    public boolean isEnabled() {
        return textField.isEnabled() || browseButton.isEnabled();
    }

    @Override
    public void setToolTipText(String text) {
        textField.setToolTipText(text);
    }

    public void setPath(String path) {
        textField.setText(path);
    }

    private void displayFileSelector(int fileSelectionMode, String baseFolder) {
        final String text;
        if (textField.getText() == null || textField.getText().isEmpty()) {
            text = baseFolder;
        } else {
            text = textField.getText();
        }
        final JFileChooser fileBrowser = new JFileChooser(text);
        fileBrowser.setFileSelectionMode(fileSelectionMode);
        fileBrowser.setFileHidingEnabled(!showHidden);
        if (fileFilter != null) {
            fileBrowser.setFileFilter(fileFilter);
        }

        int returnVal = fileBrowser.showOpenDialog(this.getParent());


        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File selectedFile = fileBrowser.getSelectedFile();
            if (fileFilter != null && fileBrowser.getFileFilter().equals(fileFilter) &&
                    !fileFilter.accept(selectedFile) &&
                    !selectedFile.exists()) {
                String newName = selectedFile.getName() + "." + fileFilter.getExtensions()[0];
                selectedFile = new File(selectedFile.getParent(), newName);
            }
            textField.setText(selectedFile.toString());
        }
    }

    @Override
    public void requestFocus() {
        this.browseButton.requestFocus();
    }

    void setText(String text) {
        textField.setText(text);
    }

    public static class InvalidPathException extends Exception {

        private final String path;

        public InvalidPathException(String msg, String path, Throwable e) {
            super(msg, e);
            this.path = path;
        }

        public String getPath() {
            return path;
        }
    }
}
