/*
 * Copyright (C) 2018, 2019, 2021, 2022 Andrew Gegg
 *
 *	This file is part of the Garden Notebook application
 *
 * The Garden Notebook application is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
	Change log
    2.3.0   Purchase selection by Product
    3.0.0	Support for Location
    		Support for Journal
	3.0.5	Use factory pattern DiaryBean
 */

package uk.co.gardennotebook;

import uk.co.gardennotebook.fxbean.*;
import uk.co.gardennotebook.spi.*;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;

/**
 *	Retrieves Item beans and stores them in a list of DiaryBeans
 * 
 * @author Andy Gegg
*	@version	3.0.5
*	@since	1.0
 */
final class DiaryBeanLister
{	
	private static final Logger LOGGER = LogManager.getLogger();

	private boolean allHusbandryClass = false;
	private List<HusbandryClassBean> husbandryClassList = Collections.emptyList();
	
	private boolean allGroundworkActivity = false;
	private List<GroundworkActivityBean> groundworkActivityList = Collections.emptyList();
	
	private boolean allAfflictionClass = false;
	private List<AfflictionClassBean> afflictionClassList = Collections.emptyList();
	
	private boolean allAffliction = false;
	private List<AfflictionBean> afflictionList = Collections.emptyList();
	
	private boolean allWeatherCondition = false;
	private List<WeatherConditionBean> weatherConditionList = Collections.emptyList();
	
	private boolean allRetailers = false;
	private List<RetailerBean> retailerList = Collections.emptyList();
	private boolean allProductCategories = false;
	private List<ProductCategoryBean> productCategoryList = Collections.emptyList();
	private boolean allProductBrands = false;
	private boolean noProductBrands = false;
	private List<ProductBrandBean> productBrandList = Collections.emptyList();
	
	private boolean allWildlifeSpecies = false;
	private List<WildlifeSpeciesBean> wildlifeSpeciesList = Collections.emptyList();
	
	private boolean allSales = false;

	private boolean allJournal = false;

	private boolean allPlants = false;
	private boolean noPlants = false;
	private List<PlantSpeciesBean> plantSpeciesList = Collections.emptyList();
	private List<PlantVarietyBean> plantVarietyList = Collections.emptyList();

	private boolean allLocations = false;
	private boolean noLocations = false;
	private List<LocationBean> locationsList = Collections.emptyList();

	LocalDate fromDate = null;
	LocalDate toDate = null;
	
	DiaryBeanLister()
	{
		
	}
	
	List<DiaryBean> fetch()
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("fetch(): fromDate:{}, toDate:{}", fromDate, toDate);
        ITrug server = TrugServer.getTrugServer().getTrug();
		
		List<DiaryBean> diaryBeanList = new ArrayList<>();
		
		ArrayList<IPlantSpecies> speciesList = new ArrayList<>(plantSpeciesList.size());
		if (!plantSpeciesList.isEmpty())
		{
			speciesList.addAll(plantSpeciesList.stream().
					filter(e -> e.get().isPresent()).
					map(e -> e.get().get()).toList());
		}
		ArrayList<IPlantVariety> varietyList = new ArrayList<>(plantVarietyList.size());
		if (!plantVarietyList.isEmpty())
		{
			varietyList.addAll(plantVarietyList.stream().
					filter(e -> e.get().isPresent()).
					map(e -> e.get().get()).toList());
		}

		List<ILocation> useLocs = new ArrayList<>(locationsList.size());
		if (!locationsList.isEmpty())
		{
			useLocs = locationsList.stream().filter(e -> e.get().isPresent()).map(e -> e.get().get()).toList();
		}

		if (allHusbandryClass || !husbandryClassList.isEmpty()){
			IHusbandryLister gal = server.getHusbandryLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
			if (!husbandryClassList.isEmpty() && !allHusbandryClass)
			{
				ArrayList<IHusbandryClass> hc = new ArrayList<>(husbandryClassList.size());
				hc.addAll(
						husbandryClassList.stream().
								filter(e -> e.get().isPresent()).
								map(e -> e.get().get()).toList()
				);
				gal.husbandryClass(hc);
			}
			if (!allPlants)
			{
				gal.plantSpecies(speciesList);
				gal.plantVariety(varietyList);
			}
			gal.location(useLocs);
			try {
				diaryBeanList.addAll(gal.fetch().stream().map(DiaryBean::from).toList());
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
LOGGER.debug("fetch(): after HU: List:{}", diaryBeanList);
		}
		
		if (allGroundworkActivity || !groundworkActivityList.isEmpty()){
			IGroundworkLister gal = server.getGroundworkLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
			if (!groundworkActivityList.isEmpty() && !allGroundworkActivity)
			{
				ArrayList<IGroundworkActivity> hc = new ArrayList<>(groundworkActivityList.size());
				hc.addAll(groundworkActivityList.stream().
						filter(e -> e.get().isPresent()).
						map(e -> e.get().get()).toList());
				gal.groundworkActivity(hc);
				
			}
			if (noPlants)
			{
				gal.requireNoPlant();
			}
			else if (!allPlants)
			{
				gal.plantSpecies(speciesList);
				gal.plantVariety(varietyList);
			}
			try {
				diaryBeanList.addAll(gal.fetch().stream().map(DiaryBean::from).toList());
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
LOGGER.debug("fetch(): after GW: List:{}", diaryBeanList);
		}
		
		if (allAffliction || !afflictionList.isEmpty()){
			IAfflictionEventLister gal = server.getAfflictionEventLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
			if (!afflictionList.isEmpty() && !allAffliction)
			{
				ArrayList<IAffliction> hc = new ArrayList<>(afflictionList.size());
				hc.addAll(afflictionList.stream().
						filter(e -> e.get().isPresent()).
						map(e -> e.get().get()).toList());
				gal.affliction(hc);
			}
			if (noPlants)
			{
				gal.requireNoPlant();
			}
			else if (!allPlants)
			{
				gal.plantSpecies(speciesList);
				gal.plantVariety(varietyList);
			}
			try {
				diaryBeanList.addAll(gal.fetch().stream().map(DiaryBean::from).toList());
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
LOGGER.debug("fetch(): after AF: List:{}", diaryBeanList);
		}
		
		if (allWeatherCondition || !weatherConditionList.isEmpty()){
			IWeatherLister gal = server.getWeatherLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
			if (!weatherConditionList.isEmpty() && !allWeatherCondition)
			{
				ArrayList<IWeatherCondition> hc = new ArrayList<>(weatherConditionList.size());
				hc.addAll(weatherConditionList.stream().
						filter(e -> e.get().isPresent()).
						map(e -> e.get().get()).toList());
				gal.weatherCondition(hc);
			}
			try {
				diaryBeanList.addAll(gal.fetch().stream().map(DiaryBean::from).toList());
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
LOGGER.debug("fetch(): after WE: List:{}", diaryBeanList);
		}
		
		if (allWildlifeSpecies || !wildlifeSpeciesList.isEmpty()){
			IWildlifeLister gal = server.getWildlifeLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
			if (!wildlifeSpeciesList.isEmpty() && !allWildlifeSpecies)
			{
				ArrayList<IWildlifeSpecies> hc = new ArrayList<>(wildlifeSpeciesList.size());
				hc.addAll(wildlifeSpeciesList.stream().
						filter(e -> e.get().isPresent()).
						map(e -> e.get().get()).toList());
				gal.wildlifeSpecies(hc);
			}
			try {
				diaryBeanList.addAll(gal.fetch().stream().map(DiaryBean::from).toList());
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
LOGGER.debug("fetch(): after WI: List:{}", diaryBeanList);
		}
        
        if (allSales && !noPlants)  //  there is no sub-setting of Sales other than by plant
        {
            ISaleLister gal = server.getSaleLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
            if (!allPlants)
            {
                    gal.plantSpecies(speciesList);
                    gal.plantVariety(varietyList);
            }

			List<ISale> sales = Collections.<ISale>emptyList();
			try {
				sales = gal.fetch();
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
            //	also need to get the SaleItems
			// if there are no sales, there's nothing to add
			// in particular, SaleItemLister.sale(empty list) returns ALL sale items!
			if (sales != null && !(sales.isEmpty()) )
			{
				diaryBeanList.addAll(sales.stream().map(DiaryBean::from).toList());

				ISaleItemLister pial = server.getSaleItemLister();
				try {
					diaryBeanList.addAll(pial.sale(sales).
							plantSpecies(speciesList).
							plantVariety(varietyList).
							fetch().stream().map(DiaryBean::from).toList());
				} catch (GNDBException ex) {
					PanicHandler.panic(ex);
				}
			}

LOGGER.debug("fetch(): after SA: List:{}", diaryBeanList);
        }

		if (allJournal )
		{
			IJournalLister gal = server.getJournalLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
			try {
				diaryBeanList.addAll(gal.fetch().stream().map(DiaryBean::from).toList());
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
			LOGGER.debug("fetch(): after WI: List:{}", diaryBeanList);
		}

		if ( (allRetailers || !retailerList.isEmpty()) &&
                (allProductCategories || !productCategoryList.isEmpty()) )
        {// must have a Retailer for a Purchase and a ProductCategory for a Product and a Product for a PurchaseItem
			IPurchaseLister gal = server.getPurchaseLister();
			gal = gal.fromDate(fromDate).toDate(toDate);
			if (!retailerList.isEmpty() && !allRetailers)
			{
				ArrayList<IRetailer> hc = new ArrayList<>(retailerList.size());
				hc.addAll(retailerList.stream().
						filter(e -> e.get().isPresent()).
						map(e -> e.get().get()).toList());
				gal.retailer(hc);
			}
            //  2.3.0
            //  now subset by products
            IProductLister pl = server.getProductLister();
            if (!allPlants)
            {
                if (noPlants)
                {
                    pl.plantSpeciesRequireNull(true);
                }
                else
                {
                    pl.plantSpecies(speciesList);
                    pl.plantVariety(varietyList);
                }
            }
            
			if (!productCategoryList.isEmpty() && !allProductCategories)
			{
				ArrayList<IProductCategory> hc = new ArrayList<>(productCategoryList.size());
				hc.addAll(productCategoryList.stream().
						filter(e -> e.get().isPresent()).
						map(e -> e.get().get()).toList());
				pl.productCategory(hc);
			}
            
            if (!allProductBrands)
            {
                if (noProductBrands)
                {
                    pl.productBrandRequireNull(true);
                }
                else
                {
                    ArrayList<IProductBrand> hc = new ArrayList<>(productBrandList.size());
                    hc.addAll(productBrandList.stream().
							filter(e -> e.get().isPresent()).
							map(e -> e.get().get()).toList());
                    pl.productBrand(hc);
                }
            }
            
            List<IProduct> prodList = Collections.<IProduct>emptyList();
            //  avoid fetching all products
            if ( !(allPlants && allProductCategories && allProductBrands) )
            {
                try {
                    prodList = pl.fetch();
                } catch (GNDBException ex) {
                    PanicHandler.panic(ex);
                }
                if (!prodList.isEmpty())
                {
                    gal.product(prodList);
                }
            }

			List<IPurchase> purs = Collections.<IPurchase>emptyList();
			try {
				purs = gal.fetch();
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
			
            //	also need to get the PurchaseItems
			// if there are no purchases, there's nothing to add
			// in particular, PurchaseItemLister.purchase(empty list) returns ALL purchase items!
			if (purs != null && !(purs.isEmpty()) )
			{
				diaryBeanList.addAll(purs.stream().map(DiaryBean::from).toList());

				IPurchaseItemLister pial = server.getPurchaseItemLister();
				try {
					diaryBeanList.addAll(pial.purchase(purs).product(prodList).fetch().stream().map(DiaryBean::from).toList());
				} catch (GNDBException ex) {
					PanicHandler.panic(ex);
				}
			}

LOGGER.debug("fetch(): after PU: List:{}", diaryBeanList);
		}
		
		diaryBeanList.sort(null);

		LOGGER.traceExit(log4jEntryMsg);
		return diaryBeanList;

	}

	public DiaryBeanLister fromDate(LocalDate val) {
		if (val == null) return this;
		this.fromDate = val;
		return this;
	}

	public DiaryBeanLister toDate(LocalDate val) {
		if (val == null) return this;
		this.toDate = val;
		return this;
	}
	
	public DiaryBeanLister allHusbandryClass() {
		allHusbandryClass = true;
		return this;
	}

	public DiaryBeanLister husbandryClass(HusbandryClassBean... gwa)
	{
		if (gwa == null) return this;
		if (gwa.length == 0) return this;
		return this.husbandryClass(Arrays.asList(gwa));
	}

	public DiaryBeanLister husbandryClass(List<HusbandryClassBean> gwa)
	{
		if (gwa == null) return this;
		if (gwa.isEmpty()) return this;
		if (husbandryClassList.isEmpty())
		{
			husbandryClassList = new ArrayList<>(gwa.size());
		}
		husbandryClassList.addAll(gwa);
		return this;
	}

	public DiaryBeanLister allGroundworkActivity() {
		allGroundworkActivity = true;
		return this;
	}

	public DiaryBeanLister groundworkActivity(GroundworkActivityBean... gwa)
	{
		if (gwa == null) return this;
		if (gwa.length == 0) return this;
		return this.groundworkActivity(Arrays.asList(gwa));
	}

	public DiaryBeanLister groundworkActivity(List<GroundworkActivityBean> gwa)
	{
		if (gwa == null) return this;
		if (gwa.isEmpty()) return this;
		if (groundworkActivityList.isEmpty())
		{
			groundworkActivityList = new ArrayList<>(gwa.size());
		}
		groundworkActivityList.addAll(gwa);
		return this;
	}

	public DiaryBeanLister allAfflictionClass() {
		allAfflictionClass = true;
		return this.allAffliction();
	}

	public DiaryBeanLister allAffliction() {
		allAffliction = true;
		return this;
	}

	public DiaryBeanLister affliction(AfflictionBean... gwa)
	{
		if (gwa == null) return this;
		if (gwa.length == 0) return this;
		return this.affliction(Arrays.asList(gwa));
	}

	public DiaryBeanLister affliction(List<AfflictionBean> gwa)
	{
		if (gwa == null) return this;
		if (gwa.isEmpty()) return this;
		if (afflictionList.isEmpty())
		{
			afflictionList = new ArrayList<>(gwa.size());
		}
		afflictionList.addAll(gwa);
		return this;
	}

	public DiaryBeanLister allWeatherCondition() {
		allWeatherCondition = true;
		return this;
	}
	
	public DiaryBeanLister weatherCondition(WeatherConditionBean... wc)
	{
		if (wc == null) return this;
		if (wc.length == 0) return this;
		return this.weatherCondition(Arrays.asList(wc));
	}

	public DiaryBeanLister weatherCondition(List<WeatherConditionBean> wc)
	{
		if (wc == null) return this;
		if (wc.isEmpty()) return this;
		if (weatherConditionList.isEmpty())
		{
			weatherConditionList = new ArrayList<>(wc.size());
		}
		weatherConditionList.addAll(wc);
		return this;
	}

	public DiaryBeanLister allRetailers() {
		allRetailers = true;
		return this;
	}
	
	public DiaryBeanLister retailer(RetailerBean... rt)
	{
		if (rt == null) return this;
		if (rt.length == 0) return this;
		return this.retailer(Arrays.asList(rt));
	}

	public DiaryBeanLister retailer(List<RetailerBean> rt)
	{
		if (rt == null) return this;
		if (rt.isEmpty()) return this;
		if (retailerList.isEmpty())
		{
			retailerList = new ArrayList<>(rt.size());
		}
		retailerList.addAll(rt);
		return this;
	}

	public DiaryBeanLister allProductCategories() {
		allProductCategories = true;
		return this;
	}
	
	public DiaryBeanLister productCategory(ProductCategoryBean... rt)
	{
		if (rt == null) return this;
		if (rt.length == 0) return this;
		return this.productCategory(Arrays.asList(rt));
	}

	public DiaryBeanLister productCategory(List<ProductCategoryBean> rt)
	{
		if (rt == null) return this;
		if (rt.isEmpty()) return this;
		if (productCategoryList.isEmpty())
		{
			productCategoryList = new ArrayList<>(rt.size());
		}
		productCategoryList.addAll(rt);
		return this;
	}

	public DiaryBeanLister allProductBrands() {
		noProductBrands = false;
		allProductBrands = true;
		return this;
	}
	
	/**
	 * Require ONLY items with no product brand specified.
	 * This is ONLY applicable to Purchases
	 * 
	 * @return this Lister
	 */
	public DiaryBeanLister noProductBrands()
	{
		noProductBrands = true;
		allProductBrands = false;
		return this;
	}

	public DiaryBeanLister productBrand(ProductBrandBean... rt)
	{
		if (rt == null) return this;
		if (rt.length == 0) return this;
		return this.productBrand(Arrays.asList(rt));
	}

	public DiaryBeanLister productBrand(List<ProductBrandBean> rt)
	{
		if (rt == null) return this;
		if (rt.isEmpty()) return this;
		if (productBrandList.isEmpty())
		{
			productBrandList = new ArrayList<>(rt.size());
		}
		productBrandList.addAll(rt);
		return this;
	}

	public DiaryBeanLister allWildlife() {
		allWildlifeSpecies = true;
		return this;
	}

	public DiaryBeanLister wildlifeSpecies(WildlifeSpeciesBean... wc)
	{
		if (wc == null) return this;
		if (wc.length == 0) return this;
		return this.wildlifeSpecies(Arrays.asList(wc));
	}

	public DiaryBeanLister wildlifeSpecies(List<WildlifeSpeciesBean> wc)
	{
		if (wc == null) return this;
		if (wc.isEmpty()) return this;
		if (wildlifeSpeciesList.isEmpty())
		{
			wildlifeSpeciesList = new ArrayList<>(wc.size());
		}
		wildlifeSpeciesList.addAll(wc);
		return this;
	}

	public DiaryBeanLister allSales() {
		allSales = true;
		return this;
	}

	public DiaryBeanLister allJournal() {
		allJournal = true;
		return this;
	}

	public DiaryBeanLister allPlants()
	{
		allPlants = true;
		noPlants = false;
		return this;
	}

	/**
	 * Require ONLY items with no plant species/variety specified.
	 * This is ONLY applicable to Purchases, Afflictions and Groundwork.
	 * 
	 * @return this Lister
	 */
	public DiaryBeanLister noPlants()
	{
		noPlants = true;
		allPlants = false;
		return this;
	}

	public DiaryBeanLister plantSpecies(PlantSpeciesBean... psl)
	{
		if (psl == null) return this;
		if (psl.length == 0) return this;
		return this.plantSpecies(Arrays.asList(psl));
	}

	public DiaryBeanLister plantSpecies(List<PlantSpeciesBean> psl)
	{
		if (psl == null) return this;
		if (psl.isEmpty()) return this;
		if (plantSpeciesList.isEmpty())
		{
			plantSpeciesList = new ArrayList<>(psl.size());
		}
		plantSpeciesList.addAll(psl);
		return this;
	}

	public DiaryBeanLister plantVariety(PlantVarietyBean... pvl)
	{
		if (pvl == null) return this;
		if (pvl.length == 0) return this;
		return this.plantVariety(Arrays.asList(pvl));
	}

	public DiaryBeanLister plantVariety(List<PlantVarietyBean> pvl)
	{
		if (pvl == null) return this;
		if (pvl.isEmpty()) return this;
		if (plantVarietyList.isEmpty())
		{
			plantVarietyList = new ArrayList<>(pvl.size());
		}
		plantVarietyList.addAll(pvl);
		return this;
	}

	public DiaryBeanLister plantCatalogue(PlantCatalogueBean... pbl)
	{
		if (pbl == null) return this;
		if (pbl.length == 0) return this;
		return this.plantCatalogue(Arrays.asList(pbl));
	}
		
	public DiaryBeanLister plantCatalogue(List<PlantCatalogueBean> pbl)
	{
		if (pbl == null) return this;
		if (pbl.isEmpty()) return this;
		
		//	typically, only choose a single species or variety if not everything
		if (pbl.size() == 1)
		{
			PlantCatalogueBean pcb = pbl.get(0);
			return switch (pcb.getItemType())
					{
						case PLANTSPECIES -> this.plantSpecies((PlantSpeciesBean) pcb.getItem());
						case PLANTVARIETY -> this.plantVariety((PlantVarietyBean) pcb.getItem());
						default -> throw new IllegalArgumentException("DiaryBeanLister: plantCatalogue: bean has unexpected type: " + pcb.getItemType());
					};
		}
		
		this.plantSpecies( pbl.stream().
							filter(p -> p.getItemType() == NotebookEntryType.PLANTSPECIES).
							map(p -> (PlantSpeciesBean)(p.getItem())).
							collect(Collectors.toList()) );
		
		this.plantVariety( pbl.stream().
							filter(p -> p.getItemType() == NotebookEntryType.PLANTVARIETY).
							map(p -> (PlantVarietyBean)(p.getItem())).
							collect(Collectors.toList()) );
		
		return this;
	}

	public DiaryBeanLister allLocations()
	{
		allLocations = true;
		noLocations = false;
		return this;
	}

	/**
	 * Require ONLY items with no plant species/variety specified.
	 * This is ONLY applicable to Purchases, Afflictions and Groundwork.
	 *
	 * @return this Lister
	 */
	public DiaryBeanLister noLocations()
	{
		noLocations = true;
		allLocations = false;
		return this;
	}

	public DiaryBeanLister locations(LocationBean... psl)
	{
		if (psl == null) return this;
		if (psl.length == 0) return this;
		return this.locations(Arrays.asList(psl));
	}

	public DiaryBeanLister locations(List<LocationBean> psl)
	{
		if (psl == null) return this;
		if (psl.isEmpty()) return this;
		if (locationsList.isEmpty())
		{
			locationsList = new ArrayList<>(psl.size());
		}
		locationsList.addAll(psl);
		return this;
	}


}
