package jforex.indicators;

import java.awt.Color;
import java.text.DecimalFormat;

import com.dukascopy.api.IBar;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.indicators.*;

public class CycleIdentifierJForex implements IIndicator {
	private IndicatorInfo indicatorInfo;
	private InputParameterInfo[] inputParameterInfos;
	private OptInputParameterInfo[] optInputParameterInfos;
	private OutputParameterInfo[] outputParameterInfos;
	private double[][][] inputs = new double[1][][];
	private int lookback = 300;
	private int buffer = 50;
	private double[][] outputs = new double[6][];

	// opt inputs
	private int PriceActionFilter = 1;
	private int Length = 3;
	private int MajorCycleStrength = 4;
	private boolean UseCycleFilter = false;
	private int UseFilterSMAorRSI = 1;
	private int FilterStrengthSMA = 12;
	private int FilterStrengthRSI = 21;

	// ind values
	double CyclePrice = (0.0);
	double Strength = (0.0);
	double SweepA = (0.0);
	double SweepB = (0.0);
	int Switch = (0);
	int Switch2 = (0);
	int SwitchA = (0);
	int SwitchB = (0);
	int SwitchC = (0);
	int SwitchD = (0);
	int SwitchE = (0);
	int SwitchAA = (0);
	int SwitchBB = (0);
	double Price1BuyA = (0.0);
	double Price2BuyA = (0.0);
	double Price1BuyB = (1.0);
	double Price2BuyB = (1.0);
	double Price1SellA = (0.0);
	double Price2SellA = (0.0);
	double Price1SellB = (0.0);
	double Price2SellB = (0.0);
	boolean ActiveSwitch = (true);
	boolean BuySwitchA = (false);
	boolean BuySwitchB = (false);
	boolean SellSwitchA = (false);
	boolean SellSwitchB = (false);
	int BuySellFac = 1;
	boolean Condition1 = true;
	boolean Condition2 = true;
	boolean Condition3 = true;
	boolean Condition6 = true;

	// output buffers
	// TODO: reinit on every calculation?
	public double[] LineBuffer = new double[4000];
	public double[] MajorCycleBuy = new double[4000];
	public double[] MajorCycleSell = new double[4000];
	public double[] MinorCycleBuy = new double[4000];
	public double[] MinorCycleSell = new double[4000];
	public double[] ZL1 = new double[4000];

	private IConsole console;
	private IIndicator ma;
	private IIndicator rsi;

	public static final int OPEN = 0;
	public static final int CLOSE = 1;
	public static final int HIGH = 2;
	public static final int LOW = 3;
	public static final int VOLUME = 4;

	public void onStart(IIndicatorContext context) {
		indicatorInfo = new IndicatorInfo("CYCLE3", "Cycle identifier", "My indicators", false, false, false, 1, 7, 6);
		inputParameterInfos = new InputParameterInfo[] { new InputParameterInfo("Input data", InputParameterInfo.Type.PRICE) };

		optInputParameterInfos = new OptInputParameterInfo[] {
				new OptInputParameterInfo("PriceActionFilter", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(1, 0, 100, 1)),
				new OptInputParameterInfo("Length", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(3, 0, 100, 1)),
				new OptInputParameterInfo("MajorCycleStrength", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(4, 0, 100, 1)),
				new OptInputParameterInfo("UseCycleFilter", OptInputParameterInfo.Type.OTHER, new BooleanOptInputDescription(false)),
				new OptInputParameterInfo("UseFilterSMAorRSI", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(1, 0, 100, 1)),
				new OptInputParameterInfo("FilterStrengthSMA", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(12, 0, 100, 1)),
				new OptInputParameterInfo("FilterStrengthRSI", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(21, 0, 100, 1))

		};
		outputParameterInfos = new OutputParameterInfo[] {
				new OutputParameterInfo("LineBuffer", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.LINE)
				{{ this.setColor(Color.DARK_GRAY); }},
				new OutputParameterInfo("MajorCycleBuy", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.HISTOGRAM)
				{{ this.setColor(Color.GREEN); }},
				new OutputParameterInfo("MajorCycleSell", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.HISTOGRAM)
				{{ this.setColor(Color.RED); }},
				new OutputParameterInfo("MinorCycleBuy", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.HISTOGRAM)
				{{ this.setColor(Color.GREEN.darker().darker()); }},
				new OutputParameterInfo("MinorCycleSell", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.HISTOGRAM)
				{{ this.setColor(Color.RED.darker().darker()); }},
				new OutputParameterInfo("ZL1", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.LINE)
				{{ this.setShowOutput(false); }}

		};

		console = context.getConsole();
		ma = context.getIndicatorsProvider().getIndicator("MA");
		rsi = context.getIndicatorsProvider().getIndicator("RSI");
	}

	public IndicatorResult calculate(int startIndex, int endIndex) {
		// calculating startIndex taking into account lookback value
		if (startIndex - getLookback() < 0) {
			startIndex -= startIndex - getLookback();
		}
		int i, j;

		int counted_bars = 0;
		int position = 0;
		int rnglength = 0;
		double range = (0.0);
		double srange = (0.0);
		int pos = (position);
		// int j = 0;
		// int i = (0);
		int posr = 0;
		int BarNumber = 0;
		double cyclePrice1 = 0.0;
		double cyclePrice2 = 0.0;
		double cyclePrice3 = 0.0;
		double cyclePrice4 = 0.0;
		// TODO: check
		resetValues();

		position = lookback - buffer;
		if (position < 0) {
			position = (0);
		}
		rnglength = (250);

		// MA calculation
		ma.setOptInputParameter(0, PriceActionFilter);
		ma.setOptInputParameter(1, MaType.MAMA.ordinal());
		ma.setInputParameter(0, inputs[0][CLOSE]);
		double[] maOutputs = new double[inputs[0][CLOSE].length];
		ma.setOutputParameter(0, maOutputs);
		ma.calculate(0, inputs[0][CLOSE].length - 1);

		// RSI calculation
		rsi.setOptInputParameter(0, 14);
		rsi.setInputParameter(0, inputs[0][CLOSE]);
		double[] rsiOutputs = new double[inputs[0][CLOSE].length];
		rsi.setOutputParameter(0, rsiOutputs);
		rsi.calculate(0, inputs[0][CLOSE].length - 1);

		// open, close, high, low, volume

		// Print("position = " + position);

		for (pos = (int) position; pos >= 0; pos--) {

			srange = (0.0);
			j = (0);
			for (i = (int) 0; i < rnglength; i++) {
				j++;
				posr = (pos + i);
				if (posr >= getLookback()) {
					break;
				}
				srange = (srange + (inputs[0][HIGH][inputs[0][HIGH].length - 1 - posr] - inputs[0][LOW][inputs[0][LOW].length - 1 - posr]));
			}
			range = (srange / j * Length);
			BarNumber = (getLookback() - pos);
			if (BarNumber < 0) {
				BarNumber = (0);
			}
			CyclePrice = maOutputs[maOutputs.length - 1 - pos];
			if (UseFilterSMAorRSI == 1) {
				ZL1[(pos)] = (ZeroLag(CyclePrice, FilterStrengthSMA, pos));
			}
			if (UseFilterSMAorRSI == 2) {
				ZL1[(pos)] = (ZeroLag(rsiOutputs[rsiOutputs.length - 1 - pos], FilterStrengthRSI, pos));
			}
			if (ZL1[pos] > ZL1[pos + 1]) {
				SwitchC = (1);
			}
			if (ZL1[pos] < ZL1[pos + 1]) {
				SwitchC = (2);
			}
			if (BarNumber <= 1) {
				if (Strength == 0) {
					SweepA = (range);
				} else {
					SweepA = (Strength);
				}
				Price1BuyA = (CyclePrice);
				Price1SellA = (CyclePrice);
			}
			if (BarNumber > 1) {
				if (Switch > -1) {
					if (CyclePrice < Price1BuyA) {
						if (UseCycleFilter && (SwitchC == 2) && BuySwitchA) {
							MinorCycleBuy[(int) (pos + BarNumber - Price1BuyB)] = (0);
							LineBuffer[(int) (pos + BarNumber - Price1BuyB)] = (0);
						}
						if (!UseCycleFilter && BuySwitchA) {
							MinorCycleBuy[(int) (pos + BarNumber - Price1BuyB)] = (0);
							LineBuffer[(int) (pos + BarNumber - Price1BuyB)] = (0);
						}
						Price1BuyA = (CyclePrice);
						Price1BuyB = (BarNumber);
						BuySwitchA = (true);
					} else if (CyclePrice > Price1BuyA) {
						SwitchA = (int) (BarNumber - Price1BuyB);
						if (!UseCycleFilter) {
							MinorCycleBuy[(pos + SwitchA)] = (-1);
							LineBuffer[(pos + SwitchA)] = (-1);
						}
						if (UseCycleFilter && SwitchC == 1) {
							MinorCycleBuy[(pos + SwitchA)] = (-1);
							LineBuffer[(pos + SwitchA)] = (-1);
							SwitchD = (1);
						} else {
							SwitchD = (0);
						}
						BuySwitchA = (true);
						cyclePrice1 = maOutputs[maOutputs.length - 1 - pos - SwitchA];
						if (ActiveSwitch) {
							Condition1 = (CyclePrice - cyclePrice1 >= SweepA);
						} else {
							Condition1 = (CyclePrice >= cyclePrice1 * (1 + SweepA / 1000));
						}
						if (Condition1 && SwitchA >= BuySellFac) {
							Switch = (-1);
							Price1SellA = (CyclePrice);
							Price1SellB = (BarNumber);
							SellSwitchA = (false);
							BuySwitchA = (false);
						}
					}
				}
				if (Switch < 1) {
					if (CyclePrice > Price1SellA) {
						if (UseCycleFilter && SwitchC == 1 && SellSwitchA) {
							MinorCycleSell[(int) (pos + BarNumber - Price1SellB)] = (0);
							LineBuffer[(int) (pos + BarNumber - Price1SellB)] = (0);
						}
						if (!UseCycleFilter && SellSwitchA) {

							MinorCycleSell[(int) (pos + BarNumber - Price1SellB)] = (0);
							LineBuffer[(int) (pos + BarNumber - Price1SellB)] = (0);
						}
						Price1SellA = (CyclePrice);
						Price1SellB = (BarNumber);
						SellSwitchA = (true);
					} else if (CyclePrice < Price1SellA) {
						SwitchA = (int) (BarNumber - Price1SellB);
						if (!UseCycleFilter) {
							MinorCycleSell[(pos + SwitchA)] = (1);
							LineBuffer[(pos + SwitchA)] = (1);
						}
						if (UseCycleFilter && (SwitchC == 2)) {
							MinorCycleSell[(pos + SwitchA)] = (1);
							LineBuffer[(pos + SwitchA)] = (1);
							SwitchD = (2);
						} else {
							SwitchD = (0);
						}
						SellSwitchA = (true);
						cyclePrice2 = maOutputs[maOutputs.length - 1 - pos - SwitchA]; // (iMA(null, 0, PriceActionFilter, 0, MODE_SMMA,
																						// PRICE_CLOSE, pos + SwitchA));
						if (ActiveSwitch) {
							Condition1 = ((cyclePrice2 - CyclePrice) >= SweepA);
						} else {
							Condition1 = (CyclePrice <= (cyclePrice2 * (1 - SweepA / 1000)));
						}
						if (Condition1 && SwitchA >= BuySellFac) {
							Switch = (1);
							Price1BuyA = (CyclePrice);
							Price1BuyB = (BarNumber);
							SellSwitchA = (false);
							BuySwitchA = (false);
						}
					}
				}
			}
			LineBuffer[(pos)] = (0);
			MinorCycleBuy[(pos)] = (0);
			MinorCycleSell[(pos)] = (0);
			if (BarNumber == 1) {
				if (Strength == 0) {
					SweepB = (range * MajorCycleStrength);
				} else {
					SweepB = (Strength * MajorCycleStrength);
				}
				Price2BuyA = (CyclePrice);
				Price2SellA = (CyclePrice);
			}
			if (BarNumber > 1) {
				if (Switch2 > -1) {
					if (CyclePrice < Price2BuyA) {
						if (UseCycleFilter && SwitchC == 2 && BuySwitchB) {
							MajorCycleBuy[(int) (pos + BarNumber - Price2BuyB)] = (0);
						}
						if (!UseCycleFilter && BuySwitchB) {
							MajorCycleBuy[(int) (pos + BarNumber - Price2BuyB)] = (0);
						}
						Price2BuyA = (CyclePrice);
						Price2BuyB = (BarNumber);
						BuySwitchB = (true);
					} else if (CyclePrice > Price2BuyA) {
						SwitchB = (int) (BarNumber - Price2BuyB);
						if (!UseCycleFilter) {
							MajorCycleBuy[(pos + SwitchB)] = (-1);
						}
						if (UseCycleFilter && SwitchC == 1) {
							MajorCycleBuy[(pos + SwitchB)] = (-1);
							SwitchE = (1);
						} else {
							SwitchE = (0);
						}
						BuySwitchB = (true);
						cyclePrice3 = maOutputs[maOutputs.length - 1 - pos - SwitchB];
						if (ActiveSwitch) {
							Condition6 = (CyclePrice - cyclePrice3 >= SweepB);
						} else {
							Condition6 = (CyclePrice >= cyclePrice3 * (1 + SweepB / 1000));
						}
						if (Condition6 && SwitchB >= BuySellFac) {
							Switch2 = (-1);
							Price2SellA = (CyclePrice);
							Price2SellB = (BarNumber);
							SellSwitchB = (false);
							BuySwitchB = (false);
						}
					}
				}

				if (Switch2 < 1) {
					if (CyclePrice > Price2SellA) {
						if (UseCycleFilter && SwitchC == 1 && SellSwitchB) {
							MajorCycleSell[(int) (pos + BarNumber - Price2SellB)] = (0);
						}
						if (!UseCycleFilter && SellSwitchB) {
							MajorCycleSell[(int) (pos + BarNumber - Price2SellB)] = (0);
						}
						Price2SellA = (CyclePrice);
						Price2SellB = (BarNumber);
						SellSwitchB = (true);
					} else if (CyclePrice < Price2SellA) {
						SwitchB = (int) (BarNumber - Price2SellB);
						if (!UseCycleFilter) {
							MajorCycleSell[(pos + SwitchB)] = (1);
						}
						if (UseCycleFilter && SwitchC == 2) {
							MajorCycleSell[(pos + SwitchB)] = (1);
							SwitchE = (2);
						} else {
							SwitchE = (0);
						}
						SellSwitchB = (true);
						cyclePrice4 = maOutputs[maOutputs.length - 1 - pos - SwitchB];
						if (ActiveSwitch) {
							Condition6 = (cyclePrice4 - CyclePrice >= SweepB);
						} else {
							Condition6 = (CyclePrice <= cyclePrice4 * (1.0 - SweepB / 1000.0));
						}
						if (Condition6 && SwitchB >= BuySellFac) {
							Switch2 = (1);
							Price2BuyA = (CyclePrice);
							Price2BuyB = (BarNumber);
							SellSwitchB = (false);
							BuySwitchB = (false);
						}
					}
				}
			}
			LineBuffer[(pos)] = (0);
			MajorCycleSell[(pos)] = (0);
			MajorCycleBuy[(pos)] = (0);
		}

		// int outputSize = lookback - buffer;
		for (int k = outputs[0].length - 1; k >= 0; k--) {
			// this effectively reversed the buffers
			outputs[0][k] = LineBuffer[outputs[0].length - 1 - k];
			outputs[1][k] = MajorCycleBuy[outputs[0].length - 1 - k];
			outputs[2][k] = MajorCycleSell[outputs[0].length - 1 - k];
			outputs[3][k] = MinorCycleBuy[outputs[0].length - 1 - k];
			outputs[4][k] = MinorCycleSell[outputs[0].length - 1 - k];
			outputs[5][k] = ZL1[outputs[0].length - 1 - k];
		}

		/*
		if (endIndex < 100) {
			print("calculate, startIndex=" + startIndex + " endIndex=" + endIndex + " lookback=" + getLookback() + " inputs: "
					+ arrayToString(inputs[0]) + " outputs: " + arrayToString(outputs[0]));
		} else {
			print("calculate, startIndex=" + startIndex + " endIndex=" + endIndex + " lookback=" + getLookback() + " inputs: "
					+ inputs[0][0].length + " outputs: " + outputs[0].length);
		}
		*/
		return new IndicatorResult(startIndex, outputs[0].length);
	}

	private int toInt(boolean value) {
		return value ? 0 : 1;
	}

	private void resetValues() {
		CyclePrice = (0.0);
		Strength = (0.0);
		SweepA = (0.0);
		SweepB = (0.0);
		Switch = (0);
		Switch2 = (0);
		SwitchA = (0);
		SwitchB = (0);
		SwitchC = (0);
		SwitchD = (0);
		SwitchE = (0);
		SwitchAA = (0);
		SwitchBB = (0);
		Price1BuyA = (0.0);
		Price2BuyA = (0.0);
		Price1BuyB = (1.0);
		Price2BuyB = (1.0);
		Price1SellA = (0.0);
		Price2SellA = (0.0);
		Price1SellB = (0.0);
		Price2SellB = (0.0);
		ActiveSwitch = true;
		BuySwitchA = false;
		BuySwitchB = false;
		SellSwitchA = false;
		SellSwitchB = false;
		BuySellFac = (1);
		Condition1 = true;
		Condition2 = true;
		Condition3 = true;
		Condition6 = true;
	}

	public double ZeroLag(double price, int length, int pos) {
		double aa = 0.0;
		double bb = 0.0;
		double CB = 0.0;
		double CC = 0.0;
		double CA = 0.0;
		double CD = 0.0;
		if (length < 3) {
			if (true)
				return (price);
		}
		aa = (Math.exp(-1.414 * 3.14159 / length));
		bb = (2 * aa * Math.cos(1.414 * 180 / length));
		CB = (bb);
		CC = (-aa * aa);
		CA = (1 - CB - CC);
		CD = (CA * price + CB * ZL1[(pos + 1)] + CC * ZL1[(pos + 2)]);
		if (true)
			return (CD);
		return 0.0;
	}

	public IndicatorInfo getIndicatorInfo() {
		return indicatorInfo;
	}

	public InputParameterInfo getInputParameterInfo(int index) {
		if (index <= inputParameterInfos.length) {
			return inputParameterInfos[index];
		}
		return null;
	}

	public int getLookback() {
		return lookback;
	}

	public int getLookforward() {
		return 0;
	}

	public OptInputParameterInfo getOptInputParameterInfo(int index) {
		if (index <= optInputParameterInfos.length) {
			return optInputParameterInfos[index];
		}
		return null;
	}

	public OutputParameterInfo getOutputParameterInfo(int index) {
		if (index <= outputParameterInfos.length) {
			return outputParameterInfos[index];
		}
		return null;
	}

	public void setInputParameter(int index, Object array) {
		inputs[index] = (double[][]) array;
	}

	public void setOptInputParameter(int index, Object value) {
		// timePeriod = (Integer) value;
	}

	public void setOutputParameter(int index, Object array) {
		outputs[index] = (double[]) array;
	}

	private void print(Object o) {
		this.console.getOut().println(o);
	}

	private void printErr(Object o) {
		this.console.getErr().println(o);
	}

	public static String arrayToString(IBar[] arr) {
		String str = "";
		for (int r = 0; r < arr.length; r++) {
			str += "[" + r + "] " + arr[r] + "; ";
		}
		return str;
	}

	public static String arrayToString(double[] arr) {
		String str = "";
		for (int r = 0; r < arr.length; r++) {
			str += "[" + r + "] " + (new DecimalFormat("0.00000")).format(arr[r]) + "; ";
		}
		return str;
	}

	public static String arrayToString(double[][] arr) {
		String str = "";
		for (int r = 0; r < arr.length; r++) {
			for (int c = 0; c < arr[r].length; c++) {
				str += " [" + r + "][" + c + "] " + (new DecimalFormat("0.00000")).format(arr[r][c]);
			}
			str += "; ";
		}
		return str;
	}
}
