package com.ranfa.lib.songinfo;

import java.io.IOException;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ranfa.lib.concurrent.CountedThreadFactory;
import com.ranfa.main.DelesteRandomSelector;

import HajimeAPI4J.api.HajimeAPI4J;
import HajimeAPI4J.api.HajimeAPI4J.List_Params;
import HajimeAPI4J.api.HajimeAPI4J.List_Type;
import HajimeAPI4J.api.HajimeAPI4J.Music_Params;
import HajimeAPI4J.api.HajimeAPI4J.Token;
import HajimeAPI4J.api.HajimeAPIBuilder;
import HajimeAPI4J.api.util.datatype.Member;
import HajimeAPI4J.exception.NoSuchURIException;

public class FetchFromAPI {
	
	private Logger logger = LoggerFactory.getLogger(FetchFromAPI.class);
	
	
	private ExecutorService localDispatcher = Executors.newCachedThreadPool(new CountedThreadFactory(() -> "DRS", "LocalDispatcher"));
	private List<JsonNode> nodes;
	
	// constructor
	
	public FetchFromAPI(String... songnames) {
		List<JsonNode> listFutures = new ArrayList<>();
		final AtomicInteger n = new AtomicInteger(0);
		for(String songname : Arrays.asList(songnames)) {
			DelesteRandomSelector.labelInfoProgressSongName.setText("<html><body>Processing : " + songname + "</body></html>");
			Map<String, Object> data = fetchList(songname);
			if(data.getOrDefault("error", "false").equals("true")) {
				JsonNode errorNode = new ObjectMapper().valueToTree(data);
				listFutures.add(errorNode);
				continue;
			}
			int taxId = Integer.parseInt(data.get("song_id").toString());
			HajimeAPI4J impl = HajimeAPIBuilder.createDefault(Token.MUSIC)
					.addParameter(Music_Params.ID	, String.valueOf(taxId))
					.build();
			logger.info("fetch data : {}", taxId);
			
			try {
				listFutures.add(impl.get());
			} catch (NoSuchURIException | IOException | InterruptedException e) {
				logger.error("Exception while processing json.", e);
			} finally {
				DelesteRandomSelector.progressTool.setValue(n.incrementAndGet());
			}
		}
		DelesteRandomSelector.labelInfoProgressSongName.setText("");
		nodes = listFutures;
	}
	
	private Map<String, Object> fetchList(String songname) {
		HajimeAPI4J api = HajimeAPIBuilder.createDefault(Token.LIST)
				.addParameter(List_Params.TYPE, List_Type.MUSIC.toString())
				.addParameter(List_Params.SEARCH, songname)
				.build();
		List<Map<String, Object>> parse = Collections.emptyList();
		try {
			parse = new ObjectMapper().readValue( api.get().traverse(), new TypeReference<List<Map<String, Object>>>() {});
			TimeUnit.SECONDS.sleep(1);
		} catch (IOException | InterruptedException | NoSuchURIException e) {
			logger.error("Exception while processing json map");
		}
		if(parse == null) {
			parse = new ArrayList<>(1);
			Map<String, Object> tmp = new HashMap<>();
			tmp.put("error", "true");
			tmp.put("name", songname);
			parse.add(tmp);
		} else if(parse.isEmpty()) {
			parse = new ArrayList<>(1);
			Map<String, Object> tmp = new HashMap<>();
			tmp.put("error", "true");
			tmp.put("name", songname);
			parse.add(tmp);
		}
		for(Map<String, Object> tmp : parse ) {
			String normalizeApiName = Normalizer.normalize(tmp.get("name").toString(), Normalizer.Form.NFKD);
			String normalizeLocalName = Normalizer.normalize(songname, Normalizer.Form.NFKD);
			if(normalizeApiName.equalsIgnoreCase(normalizeLocalName))
				return tmp;
		}
		HashMap<String, Object> altRes = new HashMap<>();
		altRes.put("error", "true");
		altRes.put("name", songname);
		return altRes;
	}
	
	public List<Map<String, String>> getInformation() {
		List<Map<String, String>> resultList = new ArrayList<>();
		int nodeSize = nodes.size();
		try {
			TimeUnit.SECONDS.sleep(1);
			for(JsonNode node : nodes) {
				if(node == null) {
					Map<String, String> tmp = new HashMap<>();
					String errorStr = "Failed to get.";
					tmp.put("songname", errorStr);
					tmp.put("link", errorStr);
					tmp.put("lyric", errorStr);
					tmp.put("composer", errorStr);
					tmp.put("arrange", errorStr);
					tmp.put("member", errorStr);
					resultList.add(tmp);
					continue;
				}
				if(node.get("error") != null) {
					Map<String, String> tmp = new HashMap<>();
					String errorStr = "Failed to get.";
					tmp.put("songname", node.get("name").asText());
					tmp.put("link", errorStr);
					tmp.put("lyric", errorStr);
					tmp.put("composer", errorStr);
					tmp.put("arrange", errorStr);
					tmp.put("member", errorStr);
					resultList.add(tmp);
					continue;
				}
				LinkedHashMap<String, String> result = new LinkedHashMap<>();
				result.put("songname", node.get("name").asText());
				result.put("link", node.get("link").asText());
				TypeReference<List<Map<String, Object>>> typeRef = new TypeReference<List<Map<String,Object>>>() {};
				ObjectMapper mapper = new ObjectMapper();
				List<Map<String, Object>> lyricList = mapper.readValue(node.get("lyrics").traverse(), typeRef),
						composerList = mapper.readValue(node.get("composer").traverse(), typeRef),
						arrangeList = mapper.readValue(node.get("arrange").traverse(), typeRef);
				List<CompletableFuture<Void>> nameArraySupplyFutures = List.of(
						CompletableFuture.supplyAsync(() -> getArrayedNames(lyricList), localDispatcher).thenAcceptAsync(str -> result.put("lyric", str), localDispatcher),
						CompletableFuture.supplyAsync(() -> getArrayedNames(composerList), localDispatcher).thenAcceptAsync(str -> result.put("composer", str), localDispatcher),
						CompletableFuture.supplyAsync(() -> getArrayedNames(arrangeList), localDispatcher).thenAcceptAsync(str -> result.put("arrange", str), localDispatcher)
				);
				StringBuilder memberBuilder = new StringBuilder();
				for(Member tmpMember : mapper.readValue(node.get("member").traverse(), new TypeReference<List<Member>>() {})) {
					if(tmpMember.getProduction().equals("cg"))
						memberBuilder.append(tmpMember.getName()).append(",");
				}
				memberBuilder.deleteCharAt(memberBuilder.length() - 1);
				result.put("member", memberBuilder.toString());
				CompletableFuture.allOf(nameArraySupplyFutures.toArray(new CompletableFuture[] {})).join();
				resultList.add(result);
			}
		} catch(IOException | InterruptedException e) {
			logger.warn("Exception while processing json", e);
			String errorStr = "No data";
			for(int i = 0; i < nodeSize; i++) {
				Map<String, String> tmp = new HashMap<>();
				JsonNode tmpNode = nodes.get(i);
				tmp.put("songname", tmpNode.get("name").asText());
				tmp.put("link", tmpNode.get("link").asText());
				tmp.put("lyric", errorStr);
				tmp.put("composer", errorStr);
				tmp.put("arrange", errorStr);
				tmp.put("member", errorStr);
				resultList.add(tmp);
			}
		}
		resultList.stream().forEach(map -> logger.debug("data fetch complete : {}", map));
		return resultList;
	}
	
	
	private String getArrayedNames(List<Map<String, Object>> data) {
		if(data == null) {
			return "No Data";
		}
		StringBuilder builder = new StringBuilder();
		for(Map<String, Object> tmp : data) {
			builder.append(tmp.get("name").toString()).append(",");
		}
		builder.deleteCharAt(builder.length() - 1);
		return builder.toString();
	}

}
