package addOns;

import java.awt.BorderLayout;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.stream.Collectors;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.text.NumberFormatter;
import api.AddOn;
import classes.Flexibility;
import classes.HolonElement.Priority;
import ui.controller.Control;

/**
 * A short algorithm to distribute the Priorities for the whole Canvas.
 * @author tom
 *
 */


public class RandomOfferdFlexibility implements AddOn {
	
	
	
	
	
	private Control  control;
	
	private JPanel content = new JPanel();



	private class PriorityDependeces{
		public JCheckBox checkbox;
		public JSlider slider = new JSlider(JSlider.HORIZONTAL,0, 100, 50);
		
		public JLabel positive = new JLabel("0 \u2192 0(0)");
		public JLabel negative = new JLabel("0 \u2192 0(0)");
		public FlexOffered offer = new FlexOffered();
		public List<Flexibility> flexList = new ArrayList<Flexibility>();
		public PriorityDependeces(String name){
			checkbox = new JCheckBox(name, true);
		}
		public void update() {
			List<Flexibility> positiveList = flexList.stream().filter(flex -> flex.isPositive()).collect(Collectors.toList());
			offer.positive.maximumOffered = positiveList.size();
			offer.positive.actualOffered = (int)positiveList.stream().filter(flex -> (flex.offered)).count();
			List<Flexibility> negativeList = flexList.stream().filter(flex -> flex.isNegative()).collect(Collectors.toList());
			offer.negative.maximumOffered = negativeList.size();
			offer.negative.actualOffered = (int)negativeList.stream().filter(flex -> (flex.offered)).count();
			offer.updateActualProportion();
			setTarget(offer.proportion);
		}
		public void setTarget(double proprotion) {
			offer.updateTarget(proprotion);
			//Update slider
			slider.setValue((int)(offer.proportion * 100.0));
			//Update Label
			positive.setText(offer.positive.actualOffered + " \u2192 " + offer.positive.targetOffered + "(" + offer.positive.maximumOffered + ")");
			negative.setText(offer.negative.actualOffered + " \u2192 " + offer.negative.targetOffered + "(" + offer.negative.maximumOffered + ")");

		}
		public void updateCanvasToTargetAmounts() {
			List<Flexibility> positiveList = flexList.stream().filter(flex -> flex.isPositive()).collect(Collectors.toList());
			Collections.shuffle(positiveList, new Random());
			for(int i = 0; i < positiveList.size(); i++){
				positiveList.get(i).offered = (i < offer.positive.targetOffered);
			}
			List<Flexibility> negativeList = flexList.stream().filter(flex -> flex.isNegative()).collect(Collectors.toList());
			Collections.shuffle(negativeList, new Random());
			for(int i = 0; i < negativeList.size(); i++){
				negativeList.get(i).offered = (i < offer.negative.targetOffered);
			}
			if(control != null) {
				control.calculateStateAndVisualForCurrentTimeStep();
			}
		}
	}
	private class FlexOffered{
		public double proportion = 0.5;
		public FlexTypeOffered positive = new FlexTypeOffered();
		public FlexTypeOffered negative = new FlexTypeOffered();
		public void updateTarget(double proportion) {
			//Clamp between 0 and 1
			proportion = Math.min(1, Math.max(0, proportion));
			if(1 == proportion) {
				negative.targetOffered = 0;
				positive.targetOffered = positive.maximumOffered;
			}else if(0 == proportion) {
				positive.targetOffered = 0;
				negative.targetOffered = negative.maximumOffered;
			}else {
				//x * proportion = positive.maximumOffered
				int maximumAmountBothA = (int)((double)positive.maximumOffered /proportion);
				int amountOtherSide = maximumAmountBothA - positive.maximumOffered;
				if(amountOtherSide <= negative.maximumOffered) {
					negative.targetOffered = amountOtherSide;
					positive.targetOffered = positive.maximumOffered;
				}else {
					int maximumAmountBothB = (int)((double)negative.maximumOffered / (1.0 -proportion));
					int amountOtherSideB = maximumAmountBothB - negative.maximumOffered;
					positive.targetOffered = amountOtherSideB;
					negative.targetOffered = negative.maximumOffered;
				}
			}
		}
		public void updateActualProportion() {
			if(positive.actualOffered + negative.actualOffered == 0) {
				proportion = 0.5;
			}else {
				proportion = (double)positive.actualOffered / (double)(positive.actualOffered + negative.actualOffered);
			}
		}
		public double getActualProportion() {
			if(positive.actualOffered + negative.actualOffered == 0) {
				return 0.5;
			}
			return (double)positive.actualOffered / (double)(positive.actualOffered + negative.actualOffered);
		}
	}
	private class FlexTypeOffered{
		int actualOffered = 0;
		int maximumOffered = 0;
		int targetOffered = 0;
	}
	PriorityDependeces low = new PriorityDependeces("low");
	PriorityDependeces medium = new PriorityDependeces("medium");
	PriorityDependeces high = new PriorityDependeces("high");
	PriorityDependeces essential = new PriorityDependeces("essential");
	
	

	
	
	
	
	
	
	
	
	public static void main(String[] args)
	{
	      JFrame newFrame = new JFrame("exampleWindow");
	      RandomOfferdFlexibility instance = new RandomOfferdFlexibility();
	      newFrame.setContentPane(instance.getPanel());
	      newFrame.pack();
	      newFrame.setVisible(true);
	      newFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	
	
	
	public RandomOfferdFlexibility(){
		low.offer.positive.maximumOffered = low.offer.positive.actualOffered =  1000;
		low.offer.negative.maximumOffered = low.offer.negative.actualOffered = 2000;
		double distribution = 0.8; 
		low.offer.updateTarget(distribution);
		System.out.println("distribution:" + distribution + " Positive:" + low.offer.positive.targetOffered
				+ " Negative:" + low.offer.negative.targetOffered);
		System.out.println("actualDistribution:" + low.offer.getActualProportion());
		
		
		content.setLayout(new BorderLayout());
		content.add(createFlexPanel(), BorderLayout.CENTER);
		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));

		JButton buttonReload = new JButton("Reload");
		buttonReload.setToolTipText("Press to relaod all canvas changes.");
		buttonReload.addActionListener(actionEvent -> update());
		buttonPanel.add(buttonReload);
		JButton buttonRun = new JButton("Run");
		buttonRun.setToolTipText("Changes the actual offered flex to the random target amount of selected prioritys.");
		buttonRun.addActionListener(actionEvent -> run());
		buttonPanel.add(buttonRun);
		content.add(buttonPanel, BorderLayout.PAGE_END);

		//content.setPreferredSize(new Dimension(300,500));
	}
	private JPanel createFlexPanel() {
		JPanel flexPanel = new JPanel();
		flexPanel.setBorder(BorderFactory.createTitledBorder("Flexibility"));
		flexPanel.setLayout(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		c.ipadx = 10;
		//c.ipady = 100;
		
		//Label
		flexPanel.add(new JLabel("Priority:"), c);
		c.gridx++;
		flexPanel.add(new JLabel("Target:"), c);
		c.gridx++;
		flexPanel.add(new JLabel("Positive#(Available):"), c);
		c.gridx++;
		flexPanel.add(new JLabel("Negative#(Available):"), c);

		c.gridx = 0;
		c.gridy = 1;
		
		
		flexPanel.add(low.checkbox, c);c.gridx++;
		c.weightx = 1;
		flexPanel.add(createTargetSetterPanel(this.low), c);c.gridx++;
		c.weightx = 0;
		flexPanel.add(this.low.positive, c);c.gridx++;
		flexPanel.add(this.low.negative, c);
		
		c.gridx = 0;
		c.gridy = 2;
		
		flexPanel.add(medium.checkbox, c);c.gridx++;
		flexPanel.add(createTargetSetterPanel(this.medium), c);c.gridx++;
		flexPanel.add(this.medium.positive, c);c.gridx++;
		flexPanel.add(this.medium.negative, c);
		
		c.gridx = 0;
		c.gridy = 3;
		
		
		flexPanel.add(high.checkbox, c);c.gridx++;
		flexPanel.add(createTargetSetterPanel(this.high), c);c.gridx++;
		flexPanel.add(this.high.positive, c);c.gridx++;
		flexPanel.add(this.high.negative, c);
		
		c.gridx = 0;
		c.gridy = 4;
		
		flexPanel.add(essential.checkbox, c);c.gridx++;
		flexPanel.add(createTargetSetterPanel(this.essential), c);c.gridx++;
		flexPanel.add(this.essential.positive, c);c.gridx++;
		flexPanel.add(this.essential.negative, c);
		
		return flexPanel;
	}

	private JPanel createTargetSetterPanel(PriorityDependeces priorityD) {
		JPanel panel = new JPanel();
		panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
		panel.setLayout(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		c.weightx = 0;
		c.anchor = GridBagConstraints.NORTH;
		panel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
		NumberFormat doubleFormat = NumberFormat.getNumberInstance(Locale.US);
		doubleFormat.setMinimumFractionDigits(0);
		doubleFormat.setMaximumFractionDigits(2);
		doubleFormat.setRoundingMode(RoundingMode.HALF_UP);
		NumberFormatter doubleFormatter = new NumberFormatter(doubleFormat);
		doubleFormatter.setMinimum(0.0);
		doubleFormatter.setMaximum(1.0);
		//doubleFormatter.setCommitsOnValidEdit(true);
		JFormattedTextField change = new JFormattedTextField(doubleFormatter);
		change.addActionListener(ChangeEvent -> priorityD.slider.setValue((int)(Double.parseDouble(change.getValue().toString()) * 100.0)));
		change.setText("0.1");
		change.setPreferredSize(new Dimension(40,20));
		panel.add(change, c);
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 1;
		c.weightx = 1;
		priorityD.slider.setMajorTickSpacing(50);
		priorityD.slider.setMinorTickSpacing(5);
		priorityD.slider.setPaintTicks(true);
		Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>();
		labelTable.put( Integer.valueOf( 0 ), new JLabel("Positiv") );
		labelTable.put( Integer.valueOf( 100 ), new JLabel("Negativ") );
		priorityD.slider.addChangeListener(changeEvent -> {
			priorityD.offer.proportion = (double)priorityD.slider.getValue()/100.0;
			priorityD.slider.setToolTipText("" + priorityD.offer.proportion);
			change.setText("" +priorityD.offer.proportion);
			priorityD.setTarget(priorityD.offer.proportion);
		});
		priorityD.slider.setLabelTable( labelTable );
		priorityD.slider.setPaintLabels(true);
		panel.add(priorityD.slider, c);
		return panel;
	}
	
	
	
	
	
	private void run() {
		//control.getModel().getObjectsOnCanvas().stream().filter(aCps -> aCps instanceof HolonObject)
		
		if(control == null) {
			System.out.println("Nothing to do");
			return;
		}
		if(low.checkbox.isSelected()) low.updateCanvasToTargetAmounts();
		if(medium.checkbox.isSelected()) medium.updateCanvasToTargetAmounts();
		if(high.checkbox.isSelected()) high.updateCanvasToTargetAmounts();
		if(essential.checkbox.isSelected()) essential.updateCanvasToTargetAmounts();
	}
	
	
	public void update() {
		if(control == null) {
			return;
		}
		control.calculateStateAndVisualForCurrentTimeStep();
		control.updateCanvas();
		List<Flexibility> flexList = control.getSimManager().getActualFlexManager().getAllFlexWrapper().stream().filter(flexwrapper -> flexwrapper.getFlex().offered).map(flex -> flex.getFlex()).collect(Collectors.toList());
		low.flexList = flexList.stream().filter(flex -> flex.getElement().getPriority() == Priority.Low && flex.fulfillsConstrains()).collect(Collectors.toList());
		medium.flexList = flexList.stream().filter(flex -> flex.getElement().getPriority() == Priority.Medium && flex.fulfillsConstrains()).collect(Collectors.toList());
		high.flexList = flexList.stream().filter(flex -> flex.getElement().getPriority() == Priority.High && flex.fulfillsConstrains()).collect(Collectors.toList());
		essential.flexList = flexList.stream().filter(flex -> flex.getElement().getPriority() == Priority.Essential && flex.fulfillsConstrains()).collect(Collectors.toList());
		low.update();
		medium.update();
		high.update();
		essential.update();
	}
	
	
	@Override
	public JPanel getPanel() {
		return content;
	}
	@Override
	public void setController(Control control) {
		this.control = control;
		update();
	}

}
