/* 
-*- coding: latin-1 -*-

This file is part of RefactorErl.

RefactorErl is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RefactorErl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with RefactorErl.  If not, see <http://plc.inf.elte.hu/erlang/>.

The Original Code is RefactorErl.

The Initial Developer of the Original Code is Eötvös Loránd University.
Portions created  by Eötvös Loránd University and ELTE-Soft Ltd.
are Copyright 2007-2025 Eötvös Loránd University, ELTE-Soft Ltd.
and Ericsson Hungary. All Rights Reserved.
*/

package com.refactorerl.ui.presentation.dupcode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.part.ViewPart;

import com.refactorerl.ui.common.FilePosition;
import com.refactorerl.ui.common.Pair;
import com.refactorerl.ui.logic.dupcode.AlgorithmData;
import com.refactorerl.ui.logic.dupcode.CloneData;
import com.refactorerl.ui.logic.dupcode.CloneIdentifierlRequest;
import com.refactorerl.ui.logic.dupcode.GetAlgorithmDataRequest;
import com.refactorerl.ui.logic.dupcode.GetAlgorithmsRequest;
import com.refactorerl.ui.logic.queries.ListQueryRequest;
import com.refactorerl.ui.presentation.Activator;
import com.refactorerl.ui.presentation.GeneralContentProposalAdapter;

/**
 * Defines the widgets of the duplicated code analysis view based on the information it receives from RefactorErl.
 * Responsible for executing the analysis request and opening the view with the result list and an editor with the first result.   
 * @author Daniel Lukacs, 2014 ELTE IK
 *
 */
public class DupCodeView extends ViewPart {

	@Override
	public void createPartControl(Composite parent) {
		List<Pair<String, String>> algs = Activator.executeRequest(
				new GetAlgorithmsRequest(Activator.getProxy(), null), getSite().getShell());

		FormToolkit toolkit = new FormToolkit(parent.getDisplay());
		Form form = toolkit.createForm(parent);
		form.setText(Messages.DupCodeView_0);
		toolkit.decorateFormHeading(form);

		Composite headClient = toolkit.createComposite(form.getHead());
		form.setHeadClient(headClient);

		headClient.setLayout(new RowLayout(SWT.VERTICAL));

		final CCombo combo = new CCombo(headClient, SWT.FLAT | SWT.DROP_DOWN | SWT.READ_ONLY);

		final ComboViewer cv = new ComboViewer(combo);

		toolkit.adapt(cv.getCCombo());
		toolkit.paintBordersFor(cv.getCCombo());

		cv.setContentProvider(new IStructuredContentProvider() {

			private List<Pair<String, String>> input;

			@Override
			public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
				input = (List<Pair<String, String>>) newInput;
			}

			@Override
			public void dispose() {
			}

			@Override
			public Object[] getElements(Object inputElement) {
				return input.toArray();
			}
		});
		cv.setLabelProvider(new LabelProvider() {
			@Override
			public String getText(Object element) {
				return ((Pair<String, String>) element).value;
			}
		});
		cv.setInput(algs);

		cv.getCCombo().select(0);

		Button runButton = toolkit.createButton(headClient, Messages.DupCodeView_1, SWT.PUSH);

		final Composite body = form.getBody();

		final StackLayout stackLayout = new StackLayout();
		body.setLayout(stackLayout);

		List<String> modlist0 = Activator.executeRequest(
				ListQueryRequest.createModuleListQueryRequest(Activator.getProxy(), null), getSite().getShell());

		List<String> modlist = new ArrayList<>();
		for (String modFile : modlist0) {
			if (modFile.indexOf('.') >= 0)
				modlist.add(modFile.substring(0, modFile.lastIndexOf('.')));
			else
				modlist.add(modFile);
		}

		final Map<String, Composite> keyedComps = new HashMap<>();
		final Map<String, Map<String, Control>> keyedControls = new HashMap<>();
		final Map<String, List<AlgorithmData>> keyedAlgDatas = new HashMap<>();

		for (Pair<String, String> pair : algs) {

			List<AlgorithmData> algDatas = Activator.executeRequest(
					new GetAlgorithmDataRequest(pair.key, Activator.getProxy(), null), getSite().getShell());

			Pair<? extends Composite, Map<String, Control>> result = createComposite(algDatas, modlist, body, toolkit);
			keyedControls.put(pair.key, result.value);
			keyedComps.put(pair.key, result.key);
			keyedAlgDatas.put(pair.key, algDatas);
		}

		stackLayout.topControl = keyedComps.get(algs.get(0).key);
		body.layout();

		cv.addSelectionChangedListener(new ISelectionChangedListener() {

			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				final Object selected = ((StructuredSelection) event.getSelection()).getFirstElement();
				String key = ((Pair<String, String>) selected).key;
				stackLayout.topControl = keyedComps.get(key);
				body.layout();
			}
		});

		runButton.addSelectionListener(new SelectionListener() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				final Object selected = ((StructuredSelection) cv.getSelection()).getFirstElement();
				String key = ((Pair<String, String>) selected).key;

				List<AlgorithmData> algDatas = keyedAlgDatas.get(key);
				Map<String, Control> controls = keyedControls.get(key);

				List<AlgorithmData> params = extractCloneIdentifiErlRequestParams(algDatas, controls);

				openViewAndEditor(key, params);

			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// not called
			}
		});
	}

	private Pair<? extends Composite, Map<String, Control>> createComposite(List<AlgorithmData> algDatas, List<String> modlist,
			Composite parent, FormToolkit toolkit) {

		final ScrolledComposite sc1 = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);

		Composite cmp = toolkit.createComposite(sc1, SWT.NONE);

		sc1.setContent(cmp);
		sc1.setExpandVertical(true);
		sc1.setExpandHorizontal(true);

		Map<String, Control> controls = fillWithAlgorithmData(cmp, algDatas, modlist, toolkit);

		sc1.setMinSize(cmp.computeSize(SWT.DEFAULT, SWT.DEFAULT));
		sc1.pack();
		sc1.layout();
		return new Pair<>(sc1, controls);
	}

	@Override
	public void setFocus() {

	}

	private abstract class SimpleVerifyListener implements VerifyListener {

		@Override
		public void verifyText(VerifyEvent e) {

			final String text = ((Text) e.widget).getText();

			e.doit = doValidate(text, e.text);

		}

		public abstract boolean doValidate(String prefix, String current);

	}

	private Map<String, Control> fillWithAlgorithmData(Composite cmp, List<AlgorithmData> algDatas,
			List<String> modlist, FormToolkit toolkit) {

		Map<String, Control> controls = new HashMap<>();

		final GridLayout gl = new GridLayout();
		gl.numColumns = 2;
		cmp.setLayout(gl);

		GridData gd = null;
		for (AlgorithmData ad : algDatas) {

			gd = new GridData();
			gd.horizontalAlignment = SWT.FILL;
			toolkit.createLabel(cmp, ad.label).setLayoutData(gd);

			Control c = null;
			switch (ad.type) {
			case ATOM:
				c = toolkit.createText(cmp, ad.defaultValue, SWT.BORDER | SWT.FLAT);
				break;
			case ATOMS:				
				Text t = toolkit.createText(cmp, ad.defaultValue, SWT.BORDER | SWT.FLAT);
				new GeneralContentProposalAdapter(t, modlist);

				gd = new GridData();
				gd.horizontalAlignment = SWT.FILL;
				t.setLayoutData(gd);

				gd = new GridData();
				gd.horizontalAlignment = SWT.FILL;
				toolkit.createLabel(cmp, "").setLayoutData(gd); // placeholder //$NON-NLS-1$

				final Table table = toolkit.createTable(cmp, SWT.FLAT | SWT.BORDER);

				final TableColumn tc = new TableColumn(table, SWT.FLAT);

				final Text t_ = t;
				t.addSelectionListener(new SelectionListener() {

					@Override
					public void widgetSelected(SelectionEvent e) {
						// not called
					}

					@Override
					public void widgetDefaultSelected(SelectionEvent e) {
						TableItem ti = new TableItem(table, SWT.FLAT);
						ti.setText(t_.getText());
						t_.setText(""); //$NON-NLS-1$

						tc.pack();
					}
				});

				table.addSelectionListener(new SelectionListener() {

					@Override
					public void widgetSelected(SelectionEvent e) {
						table.remove(table.getSelectionIndex());
					}

					@Override
					public void widgetDefaultSelected(SelectionEvent e) {
						// called on double click
					}
				});

				c = table;
				break;
			case BOOL:
				Button b = toolkit.createButton(cmp, "", SWT.CHECK); //$NON-NLS-1$
				b.setSelection(ad.getBooleanDefault());
				c = b;
				break;
			case ENUM:
				// c = toolkit.createLabel(cmp, "NOT IMPLEMENTED YET");
				CCombo combo = new CCombo(cmp, SWT.FLAT);
				toolkit.adapt(combo);
				toolkit.paintBordersFor(combo);

				combo.setItems(ad.enumOptions);
				combo.select(Arrays.asList(ad.enumOptions).indexOf(ad.defaultValue));
				c = combo;

				break;
			case FLOAT:
				t = toolkit.createText(cmp, ad.defaultValue, SWT.BORDER | SWT.FLAT);
				t.addVerifyListener(new SimpleVerifyListener() {

					@Override
					public boolean doValidate(String prefix, String current) {
						try {
							Double.parseDouble(prefix + current);

						} catch (NumberFormatException ex) {
							return false;

						}
						return true;
					}
				});

				c = t;
				break;
			case INT:
				t = toolkit.createText(cmp, ad.defaultValue, SWT.BORDER);
				t.addVerifyListener(new SimpleVerifyListener() {

					@Override
					public boolean doValidate(String prefix, String current) {
						try {
							Integer.parseInt(current);

						} catch (NumberFormatException ex) {
							return false;

						}
						return true;
					}
				});

				c = t;
				break;
			}

			gd = new GridData();
			gd.horizontalAlignment = SWT.FILL;
			c.setLayoutData(gd);
			controls.put(ad.key, c);

		}
		return controls;
	}

	private List<AlgorithmData> extractCloneIdentifiErlRequestParams(List<AlgorithmData> algDatas,
			Map<String, Control> controls) {
		List<AlgorithmData> params = new ArrayList<>();
		for (AlgorithmData aData : algDatas) {
			Control ctrl = controls.get(aData.key);

			// reflection!
			Object value = null;
			if (ctrl instanceof CCombo) {
				value = ((CCombo) ctrl).getText();
			} else if (ctrl instanceof Table) {
				TableItem[] items = ((Table) ctrl).getItems();
				List<String> itemTexts = new ArrayList<>();
				for (TableItem item : items) {
					itemTexts.add(item.getText());
				}
				value = itemTexts;

			} else if (ctrl instanceof Text) {
				String s = ((Text) ctrl).getText();
				if (aData.type.equals(AlgorithmData.DataType.INT)) {
					value = Integer.parseInt(s);
				} else if (aData.type.equals(AlgorithmData.DataType.FLOAT)) {
					value = Double.parseDouble(s);
				} else {
					value = s;
				}

			} else if (ctrl instanceof Button) {
				value = ((Button) ctrl).getSelection();
			} else {
				throw new IllegalArgumentException(Messages.DupCodeView_5);
			}
			aData.setValue(value);
			params.add(aData);
		}
		return params;
	}

	private void openViewAndEditor(String key, List<AlgorithmData> params) {

		final CloneData cd = Activator.executeRequest(new CloneIdentifierlRequest(key, params, Activator.getProxy(), null),
				getSite().getShell());

		getSite().getShell().getDisplay().syncExec(new Runnable() {

			@Override
			public void run() {
				if (cd == null || cd.getDuplicates() == null || cd.getDuplicates().isEmpty()) {
					MessageDialog.openInformation(getSite().getShell(), Messages.DupCodeView_6,
							Messages.DupCodeView_7);
					return;
				}

				ClonesResultView view;
				try {
					view = ((ClonesResultView) getSite().getWorkbenchWindow().getActivePage()
							.showView("com.refactorerl.ui.cloneResultsView")); //$NON-NLS-1$

					view.setInput(cd);
					
					DupCodeHelper.openEditor(cd.getDuplicates().get(0), getSite().getShell());

				} catch (PartInitException e) {
					Activator.showErrorDialog(e, getSite().getShell());
				}
			}

		});

	}

}
