@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {

	private int id;
	private String yahooId;
	private String name;
	private Market market;
	private Currency currency;
	private String sector;
	private String industry;
	private double quote;
	private double yield;
	private String marketCapitalization;
	private Double yesterdayClose;
	private String changeInPercent;
	private Double yearLow;
	private Double yearHigh;
	private Double minGap;
	private Double maxGap;
	/**
	 * Is this company under read time data
	 **/
	private Boolean realTime;
	private Boolean fund;
	private Timestamp lastUpdate;
	private Double change;
	private Double gapYearLow;
	private Double gapYearHigh;
	/**
	 * Manually used by user
	 **/
	private Boolean manual;

	/**
	 * @return the last update
	 */
	public final Timestamp getLastUpdate() {
		return lastUpdate != null ? (Timestamp) lastUpdate.clone() : null;
	}

	/**
	 * @return the gap year low
	 */
	public final Double getGapYearLow() {
		if (getYearLow() != null && gapYearLow == null) {
			gapYearLow = (getQuote() / getYearLow() - 1) * 100;
		}
		return gapYearLow;
	}

	/**
	 * @return the gap year high
	 */
	public final Double getGapYearHigh() {
		if (getYearHigh() != null && gapYearHigh == null) {
			gapYearHigh = (getYearHigh() / getQuote() - 1) * 100;
		}
		return gapYearHigh;
	}

	/**
	 * @param changeInPercent the change in percent
	 */
	public final void setChangeInPercent(final String changeInPercent) {
		if (changeInPercent != null) {
			final char c = changeInPercent.charAt(0);
			final String number = c == '+'
				? changeInPercent.substring(1, changeInPercent.length() - 1)
				: changeInPercent.substring(0, changeInPercent.length() - 1);
			setChange(Double.parseDouble(number));
		}
		this.changeInPercent = changeInPercent;
	}
} @Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Equity implements Comparable<Equity> {

	/**
	 * Precision for calculation
	 **/
	private final MathContext mathContext = MathContext.DECIMAL32;
	private static final int PERCENT = 100;

	@Getter
	@Setter
	private Company company;
	@Getter
	@Setter
	private int id;
	@Getter
	@Setter
	private int portfolioId;
	@Getter
	@Setter
	private int companyId;
	@Getter
	@Setter
	private String namePersonal;
	@Getter
	@Setter
	private String sectorPersonal;
	@Getter
	@Setter
	private String industryPersonal;
	@Getter
	@Setter
	private String marketCapPersonal;
	@Getter
	@Setter
	private Double quantity;
	@Getter
	@Setter
	private Double unitCostPrice;
	@Getter
	@Setter
	private Double parity;
	@Getter
	@Setter
	private Double yieldPersonal;
	@Getter
	@Setter
	private Double parityPersonal;
	@Getter
	@Setter
	private Double stopLossLocal;
	@Getter
	@Setter
	private Double objectivLocal;
	@Getter
	@Setter
	private Frequency yieldFrequency;
	@Getter
	@Setter
	private Month yieldMonth;
	@Setter
	private Date lastUpdate;

	// Not stored in DB. Calculated at run time and cached
	private Double plusMinusValue;
	private Double value;
	private Double originalValue;
	/**
	 * Yield Year
	 **/
	private Double yieldYear;
	/**
	 * Unit cost price / yield
	 **/
	private Double yieldUnitCostPrice;
	/**
	 * Plus minus value / unit cost price
	 **/
	private Double plusMinusUnitCostPriceValue;
	/**
	 * Gap stop loss
	 **/
	private Double gapStopLossLocal;
	/**
	 * Gap objective
	 **/
	private Double gapObjectivLocal;
	/**
	 * Market Capitalization
	 **/
	private BigDecimal marketCapitalizationLocal;
	/**
	 * Market Capitalization type
	 **/
	private MarketCapitalization marketCapitalizationType;
	/**
	 * Current parity
	 **/
	private Double currentParity;

	/**
	 * Calculation of gap objective
	 *
	 * @return the cap
	 */
	public final Double getGapObjectivLocal() {
		if (gapObjectivLocal == null && getObjectivLocal() != null) {
			gapObjectivLocal = (getObjectivLocal() / company.getQuote() - 1) * PERCENT;
		}
		return gapObjectivLocal;
	}

	/**
	 * Get current value
	 *
	 * @return the value
	 */
	public final Double getValue() {
		if (value == null) {
			value = quantity * company.getQuote() * getParity();
			value = new BigDecimal(value, mathContext).doubleValue();
		}
		return value;
	}

	/**
	 * Get plus minus value / unit cost price
	 *
	 * @return the plus minus value
	 */
	public final Double getPlusMinusUnitCostPriceValue() {
		if (plusMinusUnitCostPriceValue == null) {
			plusMinusUnitCostPriceValue = getValue() - unitCostPrice * getCurrentParity() * quantity;
		}
		return plusMinusUnitCostPriceValue;
	}

	/**
	 * Get yield by year
	 *
	 * @return a yield
	 */
	public final Double getYieldYear() {
		if (yieldYear == null) {
			yieldYear = getCurrentPruYield() * getValue() / PERCENT;
		}
		return yieldYear;
	}

	/**
	 * Get plus minus value
	 *
	 * @return the plus minus value
	 */
	public Double getPlusMinusValue() {
		if (plusMinusValue == null) {
			plusMinusValue = getUnitCostPrice() == 0
				? 0.0
				: (getValue() - getOriginalValue()) / getOriginalValue() * PERCENT;
		}
		return plusMinusValue;
	}

	/**
	 * Get Yield unit cost price
	 *
	 * @return the yield
	 */
	public Double getYieldUnitCostPrice() {
		if (yieldUnitCostPrice == null) {
			yieldUnitCostPrice = getCurrentPruYield();
		}
		return yieldUnitCostPrice;
	}

	/**
	 * Get gap stop loss
	 *
	 * @return the gap
	 */
	public final Double getGapStopLossLocal() {
		if (gapStopLossLocal == null && getStopLossLocal() != null) {
			gapStopLossLocal = (company.getQuote() / getStopLossLocal() - 1) * PERCENT;
		}
		return gapStopLossLocal;
	}

	/**
	 * Get gap objective
	 *
	 * @return the gap
	 */
	public final Double getGapObjectiv() {
		if (gapObjectivLocal == null) {
			gapObjectivLocal = (getObjectivLocal() / company.getQuote() - 1) * PERCENT;
		}
		return gapObjectivLocal;
	}

	/**
	 * Set objective
	 *
	 * @param gapObjectivLocal the gap objective
	 */
	public final void setGapObjectivLocal(final Double gapObjectivLocal) {
		this.gapObjectivLocal = gapObjectivLocal;
	}

	/**
	 * Get Market capitalization
	 *
	 * @return the market cap in decimal
	 */
	private BigDecimal getMarketCapitalizationLocal() {
		if (marketCapitalizationLocal == null) {
			final BigDecimal big = getMarketCapitalizationInBigDecimal();
			marketCapitalizationLocal = big == null ? null : big.multiply(new BigDecimal(getParity()));
		}
		return marketCapitalizationLocal;
	}

	/**
	 * Get market capitalization type
	 *
	 * @return the market capitalization
	 */
	public final MarketCapitalization getMarketCapitalizationType() {
		if (marketCapitalizationType == null) {
			BigDecimal cap = getMarketCapitalizationLocal();
			if (cap != null) {
				if (cap.compareTo(new BigDecimal("200000000")) == 1) {
					marketCapitalizationType = MarketCapitalization.MEGA_CAP;
				} else if (cap.compareTo(new BigDecimal("10000000")) == 1) {
					marketCapitalizationType = MarketCapitalization.LARGE_CAP;
				} else if (cap.compareTo(new BigDecimal("2000000")) == 1) {
					marketCapitalizationType = MarketCapitalization.MID_CAP;
				} else if (cap.compareTo(new BigDecimal("250")) == 1) {
					marketCapitalizationType = MarketCapitalization.SMALL_CAP;
				} else if (cap.compareTo(new BigDecimal("50")) == 1) {
					marketCapitalizationType = MarketCapitalization.MICRO_CAP;
				} else {
					marketCapitalizationType = MarketCapitalization.NANO_CAP;
				}
			} else {
				marketCapitalizationType = MarketCapitalization.UNKNOWN;
			}
		}
		return marketCapitalizationType;
	}

	/**
	 * Get Market capitalization
	 *
	 * @return the market cap in decimal
	 */
	private BigDecimal getMarketCapitalizationInBigDecimal() {
		if (getCurrentMarketCap() != null) {
			String str = getCurrentMarketCap().intern();
			StringBuilder sb = new StringBuilder();
			char c = str.charAt(str.length() - 1);
			if (c == 'B') {
				str = str.replaceAll("B", "");
				int point = str.indexOf('.');
				int reverse = str.length() - point - 1;
				sb.append(str.replaceAll("\\.", ""));
				for (int i = 0; i < 6 - reverse; i++) {
					sb.append("0");
				}
			} else {
				if (c == 'M') {
					str = str.replaceAll("M", "");
					int point = str.indexOf('.');
					int reverse = str.length() - point - 1;
					sb.append(str.replaceAll("\\.", ""));
					for (int i = 0; i < 3 - reverse; i++) {
						sb.append("0");
					}
				} else {
					if (c == 'T') {
						str = str.replaceAll("T", "");
						int point = str.indexOf('.');
						int reverse = str.length() - point - 1;
						sb.append(str.replaceAll("\\.", ""));
						for (int i = 0; i < 9 - reverse; i++) {
							sb.append("0");
						}
					}
				}
			}
			return new BigDecimal(sb.toString());
		} else {
			return null;
		}
	}

	/**
	 * Get current sector
	 *
	 * @return the current sector
	 */
	public final String getCurrentSector() {
		return getSectorPersonal() == null ? company.getSector() : getSectorPersonal();
	}

	/**
	 * Get current industry
	 *
	 * @return the current industry
	 */
	public final String getCurrentIndustry() {
		return getIndustryPersonal() == null ? company.getIndustry() : getIndustryPersonal();
	}

	/**
	 * Get current market capitalization
	 *
	 * @return the current market cap
	 */
	public String getCurrentMarketCap() {
		return getMarketCapPersonal() == null ? company.getMarketCapitalization() : getMarketCapPersonal();
	}

	/**
	 * Set market capitalization
	 *
	 * @param marketCapitalizationType the market cpitalization type
	 */
	final void setMarketCapitalizationType(final MarketCapitalization marketCapitalizationType) {
		this.marketCapitalizationType = marketCapitalizationType;
	}

	/**
	 * Get original value
	 *
	 * @return the original value
	 */
	private Double getOriginalValue() {
		if (originalValue == null) {
			originalValue = getQuantity() * getUnitCostPrice() * getCurrentParity();
		}
		return originalValue;
	}

	/**
	 * Get current pru yield
	 *
	 * @return the yield
	 */
	private Double getCurrentPruYield() {
		double res;
		if (getYieldPersonal() != null) {
			res = getUnitCostPrice() == 0 ? 0 : getYieldPersonal() / getUnitCostPrice() * PERCENT;
		} else {
			res = getUnitCostPrice() == 0 ? 0 : getCompany().getYield() / getUnitCostPrice() * PERCENT;
		}
		res = new BigDecimal(Double.toString(res), mathContext).doubleValue();
		return res;
	}

	/**
	 * Get current yield
	 *
	 * @return the current yield
	 */
	public Double getCurrentYield() {
		final Double res = getYieldPersonal() != null
			? getYieldPersonal() / getCompany().getQuote() * PERCENT
			: getCompany().getYield() / getCompany().getQuote() * PERCENT;
		return res.isInfinite() || res.isNaN()
			? 0.0
			: new BigDecimal(Double.toString(res), mathContext).doubleValue();
	}

	/**
	 * Get current parity. If personal parity is null, will return company today parity
	 *
	 * @return the current parity
	 */
	final Double getCurrentParity() {
		if (currentParity == null) {
			currentParity = getParityPersonal() != null ? getParityPersonal() : getParity();
			currentParity = new BigDecimal(currentParity, mathContext).doubleValue();
		}
		return currentParity;
	}

	/**
	 * Get last update
	 *
	 * @return the date
	 */
	public final Date getLastUpdate() {
		return (Date) lastUpdate.clone();
	}

	/**
	 * Get current name. Return company name if personnal name is null
	 *
	 * @return the current name
	 */
	public final String getCurrentName() {
		return getNamePersonal() != null ? getNamePersonal() : company.getName();
	}

	/**
	 * Get a jsonObject of the equity
	 *
	 * @return a JSONObject
	 */
	final JsonObject getJSONObject() {
		final JsonObject json = new JsonObject();
		json.addProperty("name", getCurrentName());
		json.addProperty("unitCostPrice", getUnitCostPrice());
		json.addProperty("value", getValue());
		json.addProperty("plusMinusValue", getPlusMinusValue());
		json.addProperty("quantity", getQuantity());
		json.addProperty("yieldYear", getCurrentYield());
		json.addProperty("yieldUnitCostPrice", getYieldUnitCostPrice());
		json.addProperty("quote", getCompany().getQuote());
		json.addProperty("plusMinusUnitCostPriceValue", getPlusMinusUnitCostPriceValue());
		json.addProperty("variation", getCompany().getChange());
		return json;
	}

	@Override
	public final int compareTo(final Equity equity) {
		return getCurrentName() != null && equity.getCurrentName() != null
			? getCurrentName().compareTo(equity.getCurrentName())
			: -1;
	}
} public class Portfolio {
	/**
	 * Precision of calculation
	 **/
	private final MathContext mathContext = MathContext.DECIMAL32;
	private static final int PERCENT = 100;
	@Getter
	@Setter
	private int id;
	@Getter
	@Setter
	private int userId;
	@Getter
	@Setter
	private Currency currency;
	@Getter
	@Setter
	private Double liquidity;
	@Getter
	@Setter
	private List<Equity> equities;
	@Getter
	@Setter
	private List<ShareValue> shareValues;
	@Getter
	@Setter
	private List<Account> accounts;
	@Getter
	@Setter
	private Map<String, List<Index>> indexes;

	// Calculated
	@Getter
	private Double totalQuantity;
	@Getter
	private Double averageUnitCostPrice;
	@Getter
	private Double averageQuotePrice;
	private Double totalValue;
	private Double totalPlusMinusValue;
	private Double yieldYear;
	private Double yieldYearPerc;
	private Double totalGain;
	private Date lastCompanyUpdate;
	/**
	 * Best performance of the share value
	 **/
	private Double maxShareValue;
	/**
	 * Date of the best performance
	 **/
	private Date maxShareValueDate;
	/**
	 * Current share value gain
	 **/
	private Double currentShareValuesGain;
	/**
	 * Current share value gain in percentage
	 **/
	private Double currentShareValuesGainPorcentage;
	/**
	 * Current share value yield
	 **/
	private Double currentShareValuesYield;
	/**
	 * Current share value taxes
	 **/
	private Double currentShareValuesTaxes;
	/**
	 * Current share value taxes
	 **/
	private Double currentShareValuesVolume;
	@Getter
	private Double totalVariation;
	@Getter
	private Double totalGainToday;
	/**
	 * Map that represents data of sector chart
	 **/
	private Map<String, Double> chartSectorData;
	/**
	 * Map that represents data of capitalization chart
	 **/
	private Map<String, Double> chartCapData;
	/**
	 * Maps that represents data of share value (user + cac40 + s&p500
	 **/
	private Map<Date, Double> chartShareValueData, chartShareValueData2, chartShareValueData3;
	/**
	 * Chart objects
	 **/
	private IChart timechart, timeValueChart, piechartsector, piechartcap;

	/**
	 * Constructor
	 */
	public Portfolio() {
		totalQuantity = 0d;
		averageUnitCostPrice = 0d;
		averageQuotePrice = 0d;
		totalValue = 0d;
		totalPlusMinusValue = 0d;
		yieldYear = 0d;
		totalGain = 0d;
		yieldYearPerc = 0d;
		liquidity = 0d;
		indexes = new HashMap<>();
	}

	/**
	 * Get total value
	 *
	 * @return the total value
	 */
	public final Double getTotalValue() {
		return totalValue + getLiquidity();
	}

	/**
	 * Get total plus minus value
	 *
	 * @return the total plus minus value
	 */
	public final Double getTotalPlusMinusValue() {
		return totalPlusMinusValue;
	}

	/**
	 * Get total plus minus value in absolute
	 *
	 * @return the total plus minus value in absolute
	 */
	public final Double getTotalPlusMinusValueAbsolute() {
		return Math.abs(totalPlusMinusValue);
	}

	/**
	 * Get yield year
	 *
	 * @return the yield year
	 */
	public final Double getYieldYear() {
		return yieldYear;
	}

	/**
	 * Get total gain
	 *
	 * @return the total gain
	 */
	public final Double getTotalGain() {
		return totalGain;
	}

	/**
	 * Get Yield per year in percentage
	 *
	 * @return a double
	 */
	public final Double getYieldYearPerc() {
		return yieldYearPerc;
	}

	/**
	 * Get last company update date
	 *
	 * @return a date
	 */
	public final Date getLastCompanyUpdate() {
		if (lastCompanyUpdate != null) {
			return (Date) lastCompanyUpdate.clone();
		} else {
			return null;
		}
	}

	/**
	 * Set last company ypdate date
	 *
	 * @param lastCompanyUpdate the date
	 */
	public final void setLastCompanyUpdate(final Date lastCompanyUpdate) {
		if (lastCompanyUpdate != null) {
			this.lastCompanyUpdate = (Date) lastCompanyUpdate.clone();
		}
	}

	/**
	 * This function generates all calculated field
	 */
	public final void compute() {
		Double totalUnitCostPrice = 0d;
		Double totalAverageQuotePrice = 0d;
		Double totalOriginalValue = 0d;
		totalVariation = 0d;
		double totalValueStart = 0;
		totalGainToday = 0d;
		Date lastUpdate = null;
		if (equities != null) {
			for (final Equity equity : equities) {
				totalQuantity += equity.getQuantity();
				totalUnitCostPrice += equity.getUnitCostPrice();
				totalAverageQuotePrice += equity.getCompany().getQuote() * equity.getParity();
				totalValue += equity.getValue();
				totalOriginalValue += equity.getQuantity() * equity.getUnitCostPrice() * equity.getCurrentParity();
				yieldYear += equity.getYieldYear();
				totalGain += equity.getPlusMinusUnitCostPriceValue();
				if (equity.getCompany().getRealTime()) {
					if (lastUpdate == null) {
						lastUpdate = equity.getCompany().getLastUpdate();
					} else {
						if (equity.getCompany().getLastUpdate() != null && lastUpdate.after(equity.getCompany().getLastUpdate())) {
							lastUpdate = equity.getCompany().getLastUpdate();
						}
					}
				}
				if (equity.getCompany().getChange() != null) {
					double valueStart = equity.getValue() / (equity.getCompany().getChange() / PERCENT + 1);
					totalValueStart += valueStart;
					totalGainToday += valueStart * equity.getCompany().getChange() / PERCENT;
				}

			}
			totalVariation = totalValueStart == 0 ? totalValueStart : ((totalValueStart + totalGainToday) / totalValueStart - 1) * PERCENT;
			averageUnitCostPrice = totalUnitCostPrice / equities.size();
			averageQuotePrice = totalAverageQuotePrice / equities.size();
			totalPlusMinusValue = ((totalValue - totalOriginalValue) / totalOriginalValue) * PERCENT;
			yieldYearPerc = yieldYear / getTotalValue() * PERCENT;
			setLastCompanyUpdate(lastUpdate);
		}
	}

	public final Double getTotalGainTodayAbsolute() {
		return Math.abs(totalGainToday);
	}

	/**
	 * Get chart sector data
	 *
	 * @return a map
	 */
	protected final Map<String, Double> getChartSectorData() {
		if (chartSectorData == null) {
			final Map<String, Double> data = new HashMap<>();
			for (final Equity equity : getEquities()) {
				if (equity.getCompany().getFund()) {
					addEquityValueToMap(data, Constants.FUND, equity);
				} else {
					final String sector = equity.getCurrentSector();
					if (StringUtils.isEmpty(sector)) {
						addEquityValueToMap(data, Constants.UNKNOWN, equity);
					} else {
						addEquityValueToMap(data, sector, equity);
					}
				}
			}
			chartSectorData = new TreeMap<>();
			chartSectorData.putAll(data);
		}
		return chartSectorData;
	}

	private void addEquityValueToMap(final Map<String, Double> data, final String key, final Equity equity) {
		if (data.containsKey(key)) {
			data.put(key, data.get(key) + equity.getValue());
		} else {
			data.put(key, equity.getValue());
		}
	}

	/**
	 * Get chart share value data
	 *
	 * @return a map
	 */
	protected final Map<Date, Double> getChartShareValueData() {
		Map<Date, Double> data = new HashMap<>();
		List<ShareValue> shareValuess = getShareValues();
		int max = shareValuess.size();
		double base = shareValuess.get(max - 1).getShareValue();
		for (int i = max - 1; i != -1; i--) {
			ShareValue temp = shareValuess.get(i);
			Double value = temp.getShareValue() * PERCENT / base;
			data.put(temp.getDate(), value);
		}
		chartShareValueData = new TreeMap<>();
		chartShareValueData.putAll(data);
		return chartShareValueData;
	}

	/**
	 * Generate chart share value data
	 */
	protected final void getChartShareValueData2() {
		if (chartShareValueData2 == null && chartShareValueData3 == null) {
			Map<Date, Double> data = new HashMap<>();
			Map<Date, Double> data2 = new HashMap<>();
			List<ShareValue> shareValuess = getShareValues();
			int max = shareValuess.size();
			for (int i = max - 1; i != -1; i--) {
				ShareValue temp = shareValuess.get(i);
				Double value = temp.getPortfolioValue();
				Double liqui = temp.getLiquidities();
				data.put(temp.getDate(), value);
				if (liqui != null) {
					data2.put(temp.getDate(), liqui);
				}
			}
			chartShareValueData2 = new TreeMap<>();
			chartShareValueData2.putAll(data);
			chartShareValueData3 = new TreeMap<>();
			chartShareValueData3.putAll(data2);
		}
	}

	/**
	 * Get chart capitalization data
	 *
	 * @return a map
	 */
	protected final Map<String, Double> getChartCapData() {
		if (chartCapData == null) {
			Map<String, Double> data = new HashMap<>();
			for (Equity equity : getEquities()) {
				if (!equity.getCompany().getFund()) {
					MarketCapitalization marketCap = equity.getMarketCapitalizationType();
					if (marketCap == null) {
						addEquityValueToMap(data, Constants.UNKNOWN, equity);
					} else {
						addEquityValueToMap(data, marketCap.getValue(), equity);
					}
				} else {
					addEquityValueToMap(data, Constants.UNKNOWN, equity);
				}
			}
			chartCapData = new TreeMap<>();
			chartCapData.putAll(data);
		}
		return chartCapData;
	}

	/**
	 * Get a list of yahoo id representing equities that do not have real time data
	 *
	 * @return a list of yahoo id
	 */
	public final List<String> getCompaniesYahooIdRealTime() {
		return getEquities().stream()
			.filter(equity -> equity.getCompany().getRealTime())
			.map(equity -> equity.getCompany().getYahooId())
			.collect(Collectors.toList());
	}

	/**
	 * Add indexes
	 *
	 * @param indexes a list of indexes
	 */
	public final void addIndexes(final List<Index> indexes) {
		if (indexes.size() > 0) {
			String index = indexes.get(0).getYahooId();
			this.indexes.put(index, indexes);
		}
	}

	/**
	 * Get portfolio review. An horrible function.
	 *
	 * @return a string
	 */
	public final String getPortfolioReview() {
		final StringBuilder sb = new StringBuilder();
		sb.append("<table class=\"shareValueTableDetails\">");
		for (final Equity equity : getEquities()) {
			sb.append("<tr><td width=200px><b>")
				.append(equity.getCurrentName())
				.append("</b></td><td width=180px>")
				.append(equity.getQuantity())
				.append(" * ")
				.append(equity.getCompany().getQuote());
			if (equity.getCompany().getCurrency() != getCurrency()) {
				sb.append(" * ").append(getCurrency().getParity(equity.getCompany().getCurrency()));
			}
			sb.append("</td><td>").append(equity.getValue()).append(" (").append(getCurrency().getCode()).append(")</td></tr>");
		}
		sb.append("<tr><td colspan=3><b>Liquidity:</b> ").append(getLiquidity()).append(" (").append(getCurrency().getCode()).append(")</td></tr>");
		sb.append("<tr><td colspan=3><b>Total:</b> ").append(new BigDecimal(getTotalValue(), mathContext).doubleValue()).append(" (").append(getCurrency().getCode()).append(")</td></tr>");
		sb.append("</table>");
		return sb.toString();
	}

	/**
	 * Get pie chart sector
	 *
	 * @return the chart
	 */
	public final IChart getPieChartSector() {
		if (piechartsector == null) {
			final Map<String, Double> map = getChartSectorData();
			piechartsector = new PieChart(map);
			piechartsector.generate();
		}
		return piechartsector;
	}

	/**
	 * Get pie chart capitalization
	 *
	 * @return the chart
	 */
	public final IChart getPieChartCap() {
		if (piechartcap == null) {
			final Map<String, Double> map = getChartCapData();
			piechartcap = new PieChart(map);
			piechartcap.generate();
		}
		return piechartcap;
	}

	/**
	 * Get time value chart
	 *
	 * @return the chart
	 */
	public final IChart getTimeValueChart() {
		if (timeValueChart == null && shareValues.size() > 0) {
			getChartShareValueData2();
			timeValueChart = new TimeValueChart(chartShareValueData2, chartShareValueData3, shareValues.get(0).getDate());
			timeValueChart.generate();
		}
		return timeValueChart;
	}

	/**
	 * Get time chart
	 *
	 * @return the chart
	 */
	public final IChart getTimeChart() {
		if (timechart == null && shareValues.size() > 0) {
			Map<Date, Double> map = getChartShareValueData();
			// Modifying first element of each to make it pretty in chart
			timechart = new TimeChart(map, indexes, shareValues.get(0).getDate());
			timechart.generate();
		}
		return timechart;
	}

	/**
	 * Get one account with its name
	 *
	 * @param name the account name
	 * @return the account
	 */
	public final Optional<Account> getAccount(final String name) {
		return accounts == null
			? Optional.empty()
			: accounts.stream().filter(account -> account.getName().equals(name)).findFirst();
	}

	/**
	 * Get one account with its id
	 *
	 * @param id the id
	 * @return the account
	 */
	public final Optional<Account> getAccount(final int id) {
		return accounts == null
			? Optional.empty()
			: accounts.stream().filter(account -> account.getId() == id).findFirst();
	}

	/**
	 * Get the first account created
	 *
	 * @return the account
	 */
	public final Optional<Account> getFirstAccount() {
		return accounts == null
			? Optional.empty()
			: accounts.stream().filter(account -> !account.getDel()).findFirst();
	}

	/**
	 * Get a string containing the list of sector
	 *
	 * @return a string
	 */
	public final String getHTMLSectorByCompanies() {
		final Map<String, List<Equity>> map = getSectorByCompanies();
		return extractHTMLfromMap(map);
	}

	protected Map<String, List<Equity>> getSectorByCompanies() {
		final Map<String, List<Equity>> map = new TreeMap<>();
		List<Equity> companies;
		for (final Equity equity : getEquities()) {
			if (equity.getCompany().getFund()) {
				companies = map.getOrDefault(Constants.FUND, new ArrayList<>());
				companies.add(equity);
				map.putIfAbsent(Constants.FUND, companies);
			} else {
				final String sector = equity.getCurrentSector();
				if (StringUtils.isEmpty(sector)) {
					companies = map.getOrDefault(Constants.UNKNOWN, new ArrayList<>());
					companies.add(equity);
					map.putIfAbsent(Constants.UNKNOWN, companies);
				} else {
					companies = map.getOrDefault(sector, new ArrayList<>());
					companies.add(equity);
					map.putIfAbsent(sector, companies);
				}
			}
		}
		return map;
	}

	private String extractHTMLfromMap(final Map<String, List<Equity>> map) {
		final StringBuilder res = new StringBuilder("var companies = [");
		boolean addComma = false;
		for (final Entry<String, List<Equity>> entry : map.entrySet()) {
			if (addComma) {
				res.append(",");
			}
			addComma = true;
			res.append("'");
			entry.getValue().forEach(equity -> res.append(" - ").append(equity.getCurrentName()).append("<br>"));
			res.append("'");
		}
		res.append("];");
		return res.toString();
	}

	/**
	 * Get a string containing the list of capitalization
	 *
	 * @return a string
	 */
	public final String getHTMLCapByCompanies() {
		final Map<String, List<Equity>> map = getGapByCompanies();
		return extractHTMLfromMap(map);
	}

	protected Map<String, List<Equity>> getGapByCompanies() {
		final Map<String, List<Equity>> map = new TreeMap<>();
		List<Equity> companies;
		for (final Equity equity : getEquities()) {
			if (equity.getMarketCapitalizationType().getValue() == null || equity.getCompany().getFund()) {
				equity.setMarketCapitalizationType(MarketCapitalization.UNKNOWN);
			}
			companies = map.getOrDefault(equity.getMarketCapitalizationType().getValue(), new ArrayList<>());
			companies.add(equity);
			map.put(equity.getMarketCapitalizationType().getValue(), companies);
		}
		return map;
	}

	/**
	 * This function generates the share value info (because it muse be calculated each time)
	 */
	private void generateShareValueInfo() {
		List<ShareValue> shareValuess = getShareValues();
		if (shareValuess.size() != 0 && maxShareValue == null && maxShareValueDate == null && currentShareValuesYield == null
			&& currentShareValuesTaxes == null && currentShareValuesVolume == null && currentShareValuesGain == null
			&& currentShareValuesGainPorcentage == null) {
			ShareValue lastShareValue = shareValuess.get(0);
			ShareValue firstShareValue = shareValuess.get(shareValuess.size() - 1);
			double liquidityMov = 0;
			currentShareValuesYield = 0d;
			currentShareValuesTaxes = 0d;
			currentShareValuesVolume = 0d;

			double max = shareValuess.get(0).getShareValue();
			Date date = shareValuess.get(0).getDate();
			for (int i = 0; i <= shareValuess.size() - 1; i++) {
				ShareValue sv = shareValuess.get(i);
				double current = sv.getShareValue();
				final Account account = getAccount(sv.getAccount().getName()).orElseThrow(() -> new NotFoundException(sv.getAccount().getName()));
				if (current > max) {
					max = current;
					date = sv.getDate();
				}
				if (sv.getLiquidityMovement() != null && i != shareValuess.size() - 1) {
					liquidityMov += sv.getLiquidityMovement() * account.getParity();
				}
				if (sv.getYield() != null) {
					currentShareValuesYield += sv.getYield() * account.getParity();
				}
				if (sv.getTaxe() != null) {
					currentShareValuesTaxes += sv.getTaxe() * account.getParity();
				}
				if (sv.getBuy() != null) {
					currentShareValuesVolume += sv.getBuy() * account.getParity();
				}
				if (sv.getSell() != null) {
					currentShareValuesVolume += sv.getBuy() * account.getParity();
				}
			}
			// log.info("Liquidity Movement: " + liquidityMov);
			double depart = liquidityMov + firstShareValue.getPortfolioValue();
			double fin = lastShareValue.getPortfolioValue();
			// log.info("Valeur portefeuille départ: " + depart +
			// " ( Liquidity Movement + valeur de départ soit " + liquidityMov + " + "
			// + firstShareValue.getPortfolioValue() + ")");
			// log.info("Valeur portefeuille arrivée: " + fin);
			// log.info("Dividendes recues: " + currentShareValuesYield +
			// " (déja incluse dans le portefeuille d'arrivée");
			// log.info("Gain (arrivée - départ): " + (fin - depart));
			// log.info("Gain en pourcentage (arrivée - départ) / départ * 100: " + (fin -
			// depart)
			// / depart * 100);

			currentShareValuesGain = fin - depart;

			double last = lastShareValue.getShareValue();
			double first = firstShareValue.getShareValue();
			currentShareValuesGainPorcentage = last * PERCENT / first - PERCENT;
			maxShareValue = max;
			maxShareValueDate = date;
		}
	}

	/**
	 * Get max share value
	 *
	 * @return a double
	 */
	public final Double getMaxShareValue() {
		generateShareValueInfo();
		return maxShareValue;
	}

	/**
	 * Get max share value date
	 *
	 * @return a double
	 */
	public final Date getMaxShareValueDate() {
		generateShareValueInfo();
		return maxShareValueDate != null
			? (Date) maxShareValueDate.clone()
			: null;
	}

	/**
	 * Get current share value yield
	 *
	 * @return a double
	 */
	public final Double getCurrentShareValuesYield() {
		generateShareValueInfo();
		return currentShareValuesYield;
	}

	/**
	 * Get current share value taxes
	 *
	 * @return a double
	 */
	public final Double getCurrentShareValuesTaxes() {
		generateShareValueInfo();
		return currentShareValuesTaxes;
	}

	/**
	 * Get current share values volume
	 *
	 * @return a double
	 */
	public final Double getCurrentShareValuesVolume() {
		generateShareValueInfo();
		return currentShareValuesVolume;
	}

	/**
	 * Get current share values gain
	 *
	 * @return a double
	 */
	public final Double getCurrenShareValuesGain() {
		generateShareValueInfo();
		return currentShareValuesGain;
	}

	/**
	 * Get current share values gain in Percentage
	 *
	 * @return a double
	 */
	public final Double getCurrenShareValuesGainPorcentage() {
		generateShareValueInfo();
		return currentShareValuesGainPorcentage;
	}

	/**
	 * Get a view of the object in json
	 *
	 * @return a JSONObject
	 */
	public final JsonObject getJSONObject() {
		JsonObject json = new JsonObject();
		json.addProperty("id", getId());
		json.addProperty("userId", getUserId());
		json.add(Constants.CURRENCY, getCurrency().getJSONObject());
		json.addProperty(Constants.LIQUIDITY, getLiquidity());
		json.addProperty("lastUpdate", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(getLastCompanyUpdate()));
		json.addProperty("totalValue", getTotalValue());
		json.addProperty("totalGain", getTotalGain());
		json.addProperty("totalPlusMinusValue", getTotalPlusMinusValue());
		json.addProperty("yieldYear", getYieldYear());
		json.addProperty("yieldYearPerc", getYieldYearPerc());
		JsonArray equitiess = new JsonArray();
		for (Equity e : getEquities()) {
			equitiess.add(e.getJSONObject());
		}
		json.add("equities", equitiess);
		JsonArray shareValuess = new JsonArray();
		int i = 0;
		while (i < this.shareValues.size() && i < 11) {
			shareValuess.add(this.shareValues.get(i).getJSONObject());
			i++;
		}
		json.add("shareValues", shareValuess);
		JsonArray accs = new JsonArray();
		for (final Account acc : this.accounts) {
			accs.add(acc.getJSONObject());
		}
		json.add("accounts", accs);
		JsonObject jsonPerf = new JsonObject();
		jsonPerf.addProperty("gain", currentShareValuesGain);
		jsonPerf.addProperty("performance", currentShareValuesGainPorcentage);
		jsonPerf.addProperty("yield", currentShareValuesYield);
		jsonPerf.addProperty("taxes", currentShareValuesTaxes);
		json.add("performance", jsonPerf);

		if (getTimeChart() != null) {
			TimeChart timeChart = (TimeChart) getTimeChart();
			json.addProperty("chartShareValueColors", timeChart.getColors());
			json.addProperty("chartShareValueData", timeChart.getData());
			Date date = timeChart.getDate();
			String formatDate = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date);
			json.addProperty("chartShareValueDate", formatDate);
			json.addProperty("chartShareValueDraw", timeChart.getDraw());
		}

		PieChart pieChart = (PieChart) getPieChartSector();
		json.addProperty("chartSectorData", pieChart.getData());
		json.addProperty("chartSectorTitle", pieChart.getTitle());
		json.addProperty("chartSectorDraw", pieChart.getDraw());
		json.addProperty("chartSectorCompanies", getHTMLSectorByCompanies());

		pieChart = (PieChart) getPieChartCap();
		json.addProperty("chartCapData", pieChart.getData());
		json.addProperty("chartCapTitle", pieChart.getTitle());
		json.addProperty("chartCapDraw", pieChart.getDraw());
		json.addProperty("chartCapCompanies", getHTMLCapByCompanies());

		json.addProperty("totalVariation", totalVariation);

		return json;
	}
} public class EquityException extends Exception {

	/** Serialization **/
	private static final long serialVersionUID = 1L;
	/** Error message **/
	public static final String ENTITY_ALREADY_RECORDED = " is already listed !";

	/**
	 * Constructor
	 */
	public EquityException() {
		super();
	}

	/**
	 * Constructor
	 *
	 * @param message
	 *            the error message
	 */
	public EquityException(final String message) {
		super(message);
	}

} public class NotFoundException extends RuntimeException {

	public NotFoundException(final int id) {
		super("Id not found: " + id);
	}

	public NotFoundException(final String ticker) {
		super("Ticker not found: " + ticker);
	}
} public class YahooException extends RuntimeException {

	/**
	 * Serialization
	 **/
	private static final long serialVersionUID = 1870609472239446880L;
	/**
	 * Error message
	 **/
	public static final String ERROR = "Yahoo result null. Table is probably locked.";
	/**
	 * Error message
	 **/
	private static final String CONNECT_ERROR = "Can't connect to yahoo website";
	/**
	 * Error message
	 **/
	private static final String ENCODE_ERROR = "Url encoding did not work";

	/**
	 * Constructor
	 *
	 * @param message the error message
	 */
	public YahooException(final String message) {
		super(message);
	}

	/**
	 * Constructor
	 *
	 * @param message the error message
	 * @param e       the exception
	 */
	public YahooException(final String message, final Exception e) {
		super(message, e);
	}

	public static YahooException encodeErrorException(final Exception exception) {
		return new YahooException(ENCODE_ERROR, exception);
	}

	public static YahooException cantConnectException(final Exception exception) {
		return new YahooException(CONNECT_ERROR, exception);
	}
} @RequiredArgsConstructor
@Component
public class CompanyRepository implements DAO<Company> {

	private static final String INSERT = "fr.cph.stock.repository.CompanyRepository.insertOneCompany";
	private static final String SELECT = "fr.cph.stock.repository.CompanyRepository.selectOneCompany";
	private static final String UPDATE = "fr.cph.stock.repository.CompanyRepository.updateOneCompany";
	private static final String DELETE = "fr.cph.stock.repository.CompanyRepository.deleteOneCompany";
	private static final String SELECT_WITH_ID = "fr.cph.stock.repository.CompanyRepository.selectOneCompanyWithYahooId";
	private static final String SELECT_NOT_REAL_TIME = "fr.cph.stock.repository.CompanyRepository.selectAllCompanyNotRealTime";
	private static final String SELECT_UNUSED = "fr.cph.stock.repository.CompanyRepository.selectAllUnusedCompanyIds";

	@NonNull
	private final SqlSession session;

	@Override
	public void insert(final Company company) {
		session.insert(INSERT, company);
	}

	@Override
	public Optional<Company> select(final int id) {
		return Optional.ofNullable(session.selectOne(SELECT, id));
	}

	@Override
	public void update(final Company company) {
		session.update(UPDATE, company);
	}

	@Override
	public void delete(final Company company) {
		session.delete(DELETE, company);
	}

	public Optional<Company> selectWithYahooId(final String yahooId) {
		return Optional.ofNullable(session.selectOne(SELECT_WITH_ID, yahooId));
	}

	public List<Company> selectAllCompany(final boolean realTime) {
		final Map<String, Boolean> options = new HashMap<>();
		options.put("realTime", realTime);
		// Remove manual companies
		options.put(Constants.MANUAL, false);
		return session.selectList(SELECT_NOT_REAL_TIME, options);
	}

	public List<Integer> selectAllUnusedCompanyIds() {
		return session.selectList(SELECT_UNUSED);
	}
} @RequiredArgsConstructor
@Component
public class EquityRepository implements DAO<Equity> {

	private static final String INSERT = "fr.cph.stock.repository.EquityRepository.insertOneEquity";
	private static final String SELECT = "fr.cph.stock.repository.EquityRepository.selectOneEquity";
	private static final String UPDATE = "fr.cph.stock.repository.EquityRepository.updateOneEquity";
	private static final String DELETE = "fr.cph.stock.repository.EquityRepository.deleteOneEquity";

	@NonNull
	private final SqlSession session;

	@Override
	public final void insert(final Equity equity) {
		session.insert(INSERT, equity);
	}

	@Override
	public final Optional<Equity> select(final int id) {
		return Optional.ofNullable(session.selectOne(SELECT, id));
	}

	@Override
	public final void update(final Equity equity) {
		session.update(UPDATE, equity);
	}

	@Override
	public final void delete(final Equity equity) {
		session.delete(DELETE, equity);
	}
} @RequiredArgsConstructor
@Component
public class PortfolioRepository implements DAO<Portfolio> {

	private static final String INSERT = "fr.cph.stock.repository.PortfolioRepository.insertOnePortfolio";
	private static final String SELECT = "fr.cph.stock.repository.PortfolioRepository.selectOnePortfolio";
	private static final String UPDATE = "fr.cph.stock.repository.PortfolioRepository.updateOnePortfolio";
	private static final String DELETE = "fr.cph.stock.repository.PortfolioRepository.deleteOnePortfolio";
	private static final String SELECT_WITH_ID = "fr.cph.stock.repository.PortfolioRepository.selectPortfolioWithId";
	private static final String SELECT_EQUITY = "fr.cph.stock.repository.PortfolioRepository.selectEquityFromPortfolio";
	private static final String ACCOUNT_SELECT = "fr.cph.stock.repository.AccountRepository.selectAllAccountWithUserId";
	private static final String SHARE_VALUE_SELECT = "fr.cph.stock.repository.ShareValueRepository.selectAllValue";
	private static final String SHARE_VALUE_SELECT_FROM = "fr.cph.stock.repository.ShareValueRepository.selectShareValueFrom";
	private static final String SHARE_VALUE_SELECT_TO = "fr.cph.stock.repository.ShareValueRepository.selectShareValueFromTo";

	@NonNull
	private final SqlSession session;

	@Override
	public final void insert(final Portfolio portfolio) {
		session.insert(INSERT, portfolio);
	}

	@Override
	public final Optional<Portfolio> select(final int id) {
		return Optional.ofNullable(session.selectOne(SELECT, id));
	}

	@Override
	public final void update(final Portfolio portfolio) {
		session.update(UPDATE, portfolio);
	}

	@Override
	public final void delete(final Portfolio portfolio) {
		session.delete(DELETE, portfolio);
	}

	public final Optional<Portfolio> selectPortfolioFromUserIdWithEquities(final int userId, final Date from, final Date to) {
		Optional<Portfolio> portfolioOptional;
		portfolioOptional = Optional.ofNullable(session.selectOne(SELECT_WITH_ID, userId));
		portfolioOptional.ifPresent(portfolio -> {
			final List<Equity> equities = session.selectList(SELECT_EQUITY, portfolio.getId());
			portfolio.setEquities(equities);
			final List<Account> accounts = session.selectList(ACCOUNT_SELECT, userId);
			portfolio.setAccounts(accounts);
			if (from == null) {
				final List<ShareValue> shares = session.selectList(SHARE_VALUE_SELECT, userId);
				portfolio.setShareValues(shares);
			} else {
				final Map<String, Object> map = new HashMap<>();
				map.put("userId", userId);
				map.put("from", from);
				if (to == null) {
					final List<ShareValue> shares = session.selectList(SHARE_VALUE_SELECT_FROM, map);
					portfolio.setShareValues(shares);
				} else {
					map.put("to", to);
					final List<ShareValue> shares = session.selectList(SHARE_VALUE_SELECT_TO, map);
					portfolio.setShareValues(shares);
				}
			}
		});
		return portfolioOptional;
	}

	public final Optional<Portfolio> selectPortfolioFromUserIdWithEquities(final int userId) {
		return selectPortfolioFromUserIdWithEquities(userId, null, null);
	}
} public interface CompanyService {

	void updateCompaniesNotRealTime();

	void deleteCompany(Company company);

	String addOrUpdateCompaniesLimitedRequest(List<String> companiesYahooIdRealTime) throws YahooException;

	Optional<Company> createManualCompany(String name, String industry, String sector, Currency currency, double quote);

	Optional<Company> addOrUpdateCompany(String ticker) throws YahooException;

	void updateCompanyManual(Integer companyId, Double newQuote);

	void cleanDB();

	boolean updateAllCompanies();
} public interface EquityService {

	void createEquity(int userId, String ticker, Equity equity) throws EquityException, YahooException;

	void createManualEquity(int userId, Company company, Equity equity) throws EquityException;

	void updateEquity(int userId, String ticker, Equity equity) throws YahooException;

	void deleteEquity(Equity equity);
}
import org.springframework.stereotype.Component;
import java.util.Optional;
import fr.cph.stock.entities.Company;
import fr.cph.stock.entities.Equity;
import fr.cph.stock.entities.Portfolio;
import fr.cph.stock.exception.EquityException;
import fr.cph.stock.exception.NotFoundException;
import fr.cph.stock.exception.YahooException;
import fr.cph.stock.repository.CompanyRepository;
import fr.cph.stock.repository.EquityRepository;
import fr.cph.stock.repository.PortfolioRepository;
import fr.cph.stock.service.CompanyService;
import fr.cph.stock.service.EquityService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
/**
 * Copyright 2017 Carl-Philipp Harmant
 * <p>
 * Licensed 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 fr.cph.stock.service.impl;



@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Component
public class EquityServiceImpl implements EquityService {

	@NonNull
	private final CompanyService companyService;
	@NonNull
	private final CompanyRepository companyRepository;
	@NonNull
	private final EquityRepository equityRepository;
	@NonNull
	private final PortfolioRepository portfolioRepository;