package com.tutego.ch_02.beanQualification;

import com.tutego.ch_02.beanQualification.thumbnail.AwtNearestNeighborThumbnail;
import com.tutego.ch_02.beanQualification.thumbnail.Thumbnail;
import com.tutego.ch_02.classpathScanning.FileSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.io.UncheckedIOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

@Service
public class PhotoService {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private final FileSystem fs;

    // @Autowired
    // @Qualifier("fastThumbnailRenderer")                           <- not the optimal solution, too error-prone and tight coupling
    // @ThumbnailRendering(ThumbnailRendering.RenderingQuality.FAST) <- define a custom @Qualifier, only works with @Autowired
    private Thumbnail thumbnail;

    // Optional<T> look-a-like, older than Java 8, bad documentation
    private final ObjectProvider<Thumbnail> thumbnailProvider;

    // Map<String, T> returns all the beans of type T, can also use Set<T>, List<T>, T[]
    public PhotoService(
            FileSystem fs,
            Thumbnail thumbnail,
            Map<String, Thumbnail> thumbnailsMap,
            List<Thumbnail> listThumbnails, /* Sortable data structures can be ordered by the type's Order (annotation or functional interface) */
            ApplicationContext ctx
    ) {
        this.fs = fs;
        this.thumbnail = thumbnail;
        this.thumbnailProvider = ctx != null ? ctx.getBeanProvider(Thumbnail.class) : null;

        if (this.thumbnailProvider != null) {
            // 1. Returns the bean if unique within the application context
            // 2. NoSuchBeanDefinitionException if bean is absent
            // 3. NoUniqueBeanDefinitionException if there are multiple implementations
            this.thumbnail = this.thumbnailProvider.getObject();

            // can define a fallback, but it doesn't scale for complex dependency graphs and defeats the purpose of dependency injection
            this.thumbnail = this.thumbnailProvider.getIfAvailable(AwtNearestNeighborThumbnail::new);
        }

        if (thumbnailsMap != null) {
            thumbnailsMap.values().forEach(impl -> log.info("thumbnail implementation: " + impl.getClass().getName()));
        }
    }

    public Optional<byte[]> download(String name) {
        try {
            return Optional.of(fs.load(name + ".jpg"));
        } catch (UncheckedIOException e) {
            return Optional.empty();
        }
    }


    public String upload(byte[] imageBytes) {
        byte[] thumbnailBytes = thumbnail.thumbnail(imageBytes);

        String imageName = UUID.randomUUID().toString();
        fs.store(imageName + ".jpg", imageBytes);

        log.info("upload");
        fs.store(imageName + "-thumb.jpg", thumbnailBytes);

        return imageName;
    }
}