package eu.dnetlib.is.context;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import eu.dnetlib.data.is.context.model.Category;
import eu.dnetlib.data.is.context.model.Context;
import eu.dnetlib.data.is.context.model.CtxChildInfo;
import eu.dnetlib.data.is.context.repository.CategoryRepository;
import eu.dnetlib.data.is.context.repository.ConceptLevel0Repository;
import eu.dnetlib.data.is.context.repository.ConceptLevel1Repository;
import eu.dnetlib.data.is.context.repository.ConceptLevel2Repository;
import eu.dnetlib.data.is.context.repository.ContextRepository;
import eu.dnetlib.is.errors.InformationServiceException;

@Service
public class ContextService {

	@Autowired
	private ContextRepository contextRepository;
	@Autowired
	private CategoryRepository categoryRepository;
	@Autowired
	private ConceptLevel0Repository conceptLevel0Repository;
	@Autowired
	private ConceptLevel1Repository conceptLevel1Repository;
	@Autowired
	private ConceptLevel2Repository conceptLevel2Repository;

	public List<Context> listContexts() {
		return contextRepository.findAll()
			.stream()
			.sorted((c1, c2) -> StringUtils.compareIgnoreCase(c1.getId(), c2.getId()))
			.map(c -> {
				c.setnChilds(categoryRepository.countByParent(c.getId()));
				return c;
			})
			.collect(Collectors.toList());
	}

	public Context getContext(final String ctxId) throws InformationServiceException {
		final Context c = contextRepository.findById(ctxId).orElseThrow(() -> new InformationServiceException("Context Not found"));
		c.setnChilds(categoryRepository.countByParent(c.getId()));
		return c;
	}

	public ObjectNode getContextFull(final String ctxId) throws InformationServiceException {
		final ObjectMapper mapper = new ObjectMapper();
		final Context ctx = getContext(ctxId);

		final ObjectNode ctxNode = mapper.convertValue(ctx, ObjectNode.class);

		final ArrayNode catArray = mapper.createArrayNode();

		for (final Category cat : listCategories(ctxId)) {
			final ObjectNode catNode = mapper.convertValue(cat, ObjectNode.class);
			catArray.add(catNode);

			final ArrayNode c0Array = mapper.createArrayNode();
			for (final CtxChildInfo c0 : listConcepts(0, cat.getId())) {
				final ObjectNode c0Node = mapper.convertValue(c0, ObjectNode.class);
				c0Array.add(c0Node);

				final ArrayNode c1Array = mapper.createArrayNode();
				for (final CtxChildInfo c1 : listConcepts(1, c0.getId())) {
					final ObjectNode c1Node = mapper.convertValue(c1, ObjectNode.class);
					c1Array.add(c1Node);

					final ArrayNode c2Array = mapper.createArrayNode();
					for (final CtxChildInfo c2 : listConcepts(2, c1.getId())) {
						final ObjectNode c2Node = mapper.convertValue(c2, ObjectNode.class);
						c2Array.add(c2Node);
					}
					c1Node.set("concepts", c2Array);
				}
				c0Node.set("concepts", c1Array);
			}
			catNode.set("concepts", c0Array);
		}
		ctxNode.set("categories", catArray);

		return ctxNode;
	}

	public List<Category> listCategories(final String parent) {
		final List<Category> list = new ArrayList<>();

		categoryRepository.findByParentOrderById(parent).forEach(c -> {
			c.setnChilds(conceptLevel0Repository.countByParent(c.getId()));
			list.add(c);
		});

		return list;
	}

	public List<? extends CtxChildInfo> listConcepts(final int level, final String parent) throws InformationServiceException {
		final List<CtxChildInfo> list = new ArrayList<>();

		switch (level) {
		case 0:
			conceptLevel0Repository.findByParentOrderById(parent).forEach(c -> {
				c.setnChilds(conceptLevel1Repository.countByParent(c.getId()));
				list.add(c);
			});
			break;
		case 1:
			conceptLevel1Repository.findByParentOrderById(parent).forEach(c -> {
				c.setnChilds(conceptLevel2Repository.countByParent(c.getId()));
				list.add(c);
			});
			break;
		case 2:
			conceptLevel2Repository.findByParentOrderById(parent).forEach(c -> {
				c.setnChilds(0);
				list.add(c);
			});
			break;
		default:
			throw new InformationServiceException("Invalid concept level - valid levels are 0, 1, 2");
		}

		return list;
	}

}
