package com.xiaohansong.codemaker.ui;

import com.intellij.icons.AllIcons;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.DocumentAdapter;
import com.intellij.util.ExceptionUtil;
import com.xiaohansong.codemaker.ClassEntry;
import com.xiaohansong.codemaker.CodeTemplate;
import com.xiaohansong.codemaker.TemplateLanguage;
import com.xiaohansong.codemaker.templates.GeneratedSource;
import com.xiaohansong.codemaker.templates.PolyglotTemplateEngine;
import com.xiaohansong.codemaker.templates.TemplateEngine;
import com.xiaohansong.codemaker.templates.input.TestInput;
import com.xiaohansong.codemaker.templates.input.TestInputs;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author hansong.xhs
 * @version $Id: TemplateEditPane.java, v 0.1 2017-01-31 9:06 hansong.xhs Exp $$
 */
public class TemplateEditPane {

    private JPanel     templateEdit;
    private JTextField templateNameText;
    private JSpinner   classNumberSpinner;
    private JTextField classNameText;
    private JTextField fileEncodingText;
    private JComboBox  templateLanguage;
    private JButton    testButton;
    private JComboBox<TestInput>  testInputs;
    private JSplitPane editorSplitPane;
    private JButton showTestInput;
    private JComboBox<TargetLanguage> targetLanguage;
    private TemplateEngine templateEngine = new PolyglotTemplateEngine();
    private TemplateEditAndTest templateEditAndTest;

    public TemplateEditPane(CodeTemplate codeTemplate, Consumer<String> titleChanged) {
        if (codeTemplate == null) {
            throw new NullPointerException("codeTemplate is null!");
        }

        templateNameText.setText(codeTemplate.getName());
        templateNameText.getDocument().addDocumentListener(new DocumentAdapter() {
            @Override
            protected void textChanged(@NotNull DocumentEvent e) {
                titleChanged.accept(templateNameText.getText());
            }
        });

        templateLanguage.setSelectedItem(codeTemplate.getTemplateLanguage().name());
        templateLanguage.addActionListener(e -> {
           templateEditAndTest.refreshTemplateEditor();
        });
        classNumberSpinner.setValue(codeTemplate.getClassNumber());
        classNameText.setText(codeTemplate.getClassNameVm());
        fileEncodingText.setText(StringUtil.notNullize(codeTemplate.getFileEncoding(), CodeTemplate.DEFAULT_ENCODING));

        testButton.setText(null);
        testButton.setIcon(AllIcons.Actions.Execute);
        testButton.addActionListener(e -> testTemplate());

        final ListCellRenderer renderer = testInputs.getRenderer();
        TestInputs.getInputs().forEach(testInputs::addItem);
        //noinspection unchecked
        testInputs.setRenderer((l, item, index, selected, focused ) ->
                renderer.getListCellRendererComponent(l, item.getName(), index, selected, focused));

        testInputs.addItemListener(e -> templateEditAndTest.refreshTestInput());

        classNumberSpinner.setModel(new SpinnerNumberModel(codeTemplate.getClassNumber(), 1, 3, 1));
        classNumberSpinner.addChangeListener(e -> templateEditAndTest.refreshTestInput());

        templateEditAndTest = new TemplateEditAndTest(
                editorSplitPane,
                this::getSelectedTestInput,
                this::getClassNumber,
                codeTemplate.getCodeTemplate(),
                this::getTemplateLanguage,
                150);

        setUpShowTestInputButton();

        TargetLanguageSelect.initCombo(targetLanguage, codeTemplate.getTargetLanguage());
    }

    protected void setUpShowTestInputButton() {
        showTestInput.setIcon(AllIcons.General.ArrowDown);
        showTestInput.setToolTipText("Show Test Input");
        showTestInput.addActionListener(e -> {
            if(!templateEditAndTest.getTestInputShown()) {
                templateEditAndTest.toggleTestInputPane();
                showTestInput.setIcon(AllIcons.General.ArrowUp);
                editorSplitPane.setEnabled(true);
                showTestInput.setToolTipText("Hide Test Input");
            } else {
                templateEditAndTest.toggleTestInputPane();
                showTestInput.setIcon(AllIcons.General.ArrowDown);
                showTestInput.setToolTipText("Show Test Input");
                editorSplitPane.setEnabled(false);
            }
        });
    }

    private void testTemplate() {
        CodeTemplate template = new CodeTemplate(getTemplateName(), getClassName(), getTemplate(),
                getClassNumber(), getFileEncoding(), getTemplateLanguage(), getTargetLanguage());

        final List<ClassEntry> classEntries = inputClassIndices()
                .map(i ->
                     getSelectedTestInput().createInput("SomeClass" + i)
                )
                .collect(Collectors.toList());

        Object result;
        try {
            result = templateEngine.evaluate(template, classEntries, classEntries.get(0));
        } catch (Throwable e) {
            result = e;
        }

        final DialogBuilder builder = new DialogBuilder(this.templateEdit);
        builder.addCloseButton().setText("Close");

        if(result instanceof GeneratedSource) {
            builder.setTitle(((GeneratedSource) result).getClassName());
            builder.setCenterPanel(
                    Editors.createSourceEditor(null, getTargetLanguage(), ((GeneratedSource) result).getContent(), true).getComponent());
        } else {
            Throwable error = (Throwable) result;
            builder.setTitle("Failed!");
            builder.setCenterPanel(
                  Editors.createSourceEditor(null, "txt",
                          error.getMessage() + "\n\n" + ExceptionUtil.getThrowableText(error), true)
                          .getComponent());
        }
        builder.show();
    }

    @NotNull
    private Stream<Integer> inputClassIndices() {
        return Stream.iterate(1, i -> i + 1)
                .limit(getClassNumber());
    }

    private TestInput getSelectedTestInput() {
        return testInputs.getItemAt(testInputs.getSelectedIndex());
    }

    /**
     * Getter method for property <tt>templateEdit</tt>.
     *
     * @return property value of templateEdit
     */
    public JPanel getTemplateEdit() {
        return templateEdit;
    }

    public String getClassName() {
        return classNameText.getText();
    }

    public String getTemplateName() {
        return templateNameText.getText();
    }

    public TemplateLanguage getTemplateLanguage() {
        return TemplateLanguage.valueOf(String.valueOf(templateLanguage.getSelectedItem()));
    }

    public String getTemplate() {
        return templateEditAndTest.getTemplateText();
    }

    public String getFileEncoding() {
        return fileEncodingText.getText();
    }

    public int getClassNumber() {
        return ((Number)classNumberSpinner.getValue()).intValue();
    }

    public String getTargetLanguage() {
        final TargetLanguage selectedItem = (TargetLanguage)targetLanguage.getSelectedItem();
        if(selectedItem == null) return "java";
        else return selectedItem.getFileType();
    }
}