package com.kubakhov.data.service;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public abstract class AbstarctSpecificationsService<T, ID extends Serializable>
		implements BaseService<T, Serializable> {

	private final Set<Specification<T>> requiredSpecifications = new HashSet<Specification<T>>();

	private final Set<Specification<T>> additionalSpecifications = new HashSet<Specification<T>>();

	public abstract BaseService<T, Serializable> getBaseSerive();

	protected abstract Specification<T> getSpecificationService(Specification<T> spec);

	@Override
	public Page<T> findAll(Pageable pageable) {
		return this.findAll((Specification<T>) null, pageable);
	}

	@Override
	public <S extends T> S save(S entity) {
		return getBaseSerive().save(entity);
	}

	@Override
	public T findOne(Serializable id) {
		return getBaseSerive().findOne(id);
	}

	@Override
	public boolean exists(Serializable id) {
		return getBaseSerive().exists(id);
	}

	@Override
	public long count() {
		return this.count((Specification<T>) null);
	}

	@Override
	public void delete(Serializable id) {
		getBaseSerive().delete(id);
	}

	@Override
	public void delete(T entity) {
		getBaseSerive().delete(entity);
	}

	@Override
	public void delete(Iterable<? extends T> entities) {
		getBaseSerive().delete(entities);
	}

	@Override
	public void deleteAll() {
		getBaseSerive().deleteAll();
	}

	@Override
	public T findOne(Specification<T> spec) {
		return getBaseSerive().findOne(getInternalSpecification(spec));
	}

	@Override
	public List<T> findAll(Specification<T> spec) {
		return getBaseSerive().findAll(getInternalSpecification(spec));
	}

	@Override
	public Page<T> findAll(Specification<T> spec, Pageable pageable) {
		return getBaseSerive().findAll(getInternalSpecification(spec), pageable);
	}

	@Override
	public List<T> findAll(Specification<T> spec, Sort sort) {
		return getBaseSerive().findAll(getInternalSpecification(spec), sort);
	}

	@Override
	public long count(Specification<T> spec) {
		return getBaseSerive().count(getInternalSpecification(spec));
	}

	@Override
	public List<T> findAll() {
		return this.findAll((Specification<T>) null);
	}

	@Override
	public List<T> findAll(Sort sort) {
		return this.findAll((Specification<T>) null, sort);
	}

	@Override
	public List<T> findAll(Iterable<Serializable> ids) {
		return getBaseSerive().findAll(ids);
	}

	@Override
	public <S extends T> List<S> save(Iterable<S> entities) {
		return getBaseSerive().save(entities);
	}

	@Override
	public void flush() {
		getBaseSerive().flush();
	}

	@Override
	public <S extends T> S saveAndFlush(S entity) {
		return getBaseSerive().saveAndFlush(entity);
	}

	@Override
	public void deleteInBatch(Iterable<T> entities) {
		getBaseSerive().deleteInBatch(entities);
	}

	@Override
	public void deleteAllInBatch() {
		getBaseSerive().deleteAllInBatch();
	}

	@Override
	public T getOne(Serializable id) {
		return getBaseSerive().getOne(id);
	}

	@Override
	public Serializable getId(T entity) {
		return getBaseSerive().getId(entity);
	}

	@Override
	public T create() throws Exception {
		return getBaseSerive().create();
	}

	@Override
	public Class<T> getType() {
		return getBaseSerive().getType();
	}

	private Specification<T> getInternalSpecification(Specification<T> spec) {
		return getSpecificationService(spec);
	}

	public Set<Specification<T>> getSpecifications() {
		HashSet<Specification<T>> specifications = new HashSet<>();
		specifications.addAll(requiredSpecifications);
		specifications.addAll(additionalSpecifications);
		return Collections.unmodifiableSet(specifications);
	}

	public Set<Specification<T>> getRequiredSpecifications() {
		return Collections.unmodifiableSet(requiredSpecifications);
	}

	public Set<Specification<T>> getAdditionalSpecifications() {
		return Collections.unmodifiableSet(additionalSpecifications);
	}

	public void addRequiredSpecification(Specification<T> spec) {
		requiredSpecifications.add(spec);
	}

	public void removeRequiredSpecification(Specification<T> spec) {
		requiredSpecifications.remove(spec);
	}

	public void clearRequiredSpecification() {
		requiredSpecifications.clear();
	}

	public void addAdditionalSpecification(Specification<T> spec) {
		additionalSpecifications.add(spec);
	}

	public void removeAdditionalSpecification(Specification<T> spec) {
		additionalSpecifications.remove(spec);
	}

	public void clearAdditionalSpecification() {
		additionalSpecifications.clear();
	}
}
