package fr.umlv.json;

import static java.lang.Integer.parseInt;
import static java.util.regex.Pattern.compile;
import static java.util.stream.Collectors.joining;
import static java.util.stream.IntStream.rangeClosed;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class IncompleteJSONParser {
	private enum Kind {
		STRING("\"([a-zA-Z0-9\\-]*)\""),
		INTEGER("([0-9]+)"),
		LEFT_CURLY("(\\{)"),
		RIGHT_CURLY("(\\})"),
		COLON("(\\:)"),
		COMMA("(\\,)"),
		BLANK("([ \t]+)")
		;
		
		private final String regex;
		
		private Kind(String regex) {
			this.regex = regex;
		}
		
		private final static Kind[] VALUES = values();
	}
	
	private static class Token {
		private final Kind kind;
		private final String text;
		
		private Token(Kind kind, String text) {
			this.kind = kind;
			this.text = text;
		}
		
		private boolean is(Kind kind) {
			return this.kind == kind;
		}
		
		private String expect(Kind kind) {
			if (this.kind != kind) {
				throw new IllegalStateException("expect " + kind + " but recognized " + this.kind);
			}
			return text;
		}
	}
	
	private static class Lexer {
		private final Matcher matcher;

		private Lexer(Matcher matcher) {
			this.matcher = matcher;
		}

		private Token next() {
			for(;;) {
				if (!matcher.find()) {
					throw new IllegalStateException("no token recognized");
				}
				var index = rangeClosed(1, matcher.groupCount()).filter(i -> matcher.group(i) != null).findFirst().orElseThrow();
				var kind = Kind.VALUES[index - 1];
				if (kind != Kind.BLANK) {
					return new Token(kind, matcher.group(index));
				}
			}
		}
	}
	
	private static final Pattern PATTERN = compile(Arrays.stream(Kind.VALUES).map(k -> k.regex).collect(joining("|")));
	
	public static Map<String, Object> parse(String input) {
		var map = new HashMap<String, Object>();
		var lexer = new Lexer(PATTERN.matcher(input));
		lexer.next().expect(Kind.LEFT_CURLY);
		var token = lexer.next();
	  if (token.is(Kind.RIGHT_CURLY)) {
	  	return map;
	  }
	  for(;;) {
	  	var key = token.expect(Kind.STRING);
	  	lexer.next().expect(Kind.COLON);
	  	token = lexer.next();
	  	var value = token.is(Kind.INTEGER)? parseInt(token.text): token.expect(Kind.STRING);
	  	map.put(key, value);
	  	token = lexer.next();
	  	if (token.is(Kind.RIGHT_CURLY)) {
	  		return map;
	  	}
	  	token.expect(Kind.COMMA);
	  	token = lexer.next();
	  }
	}
	
	public static void main(String[] args) {
		System.out.println(parse("{}"));
		System.out.println(parse("{ }"));
		System.out.println(parse("{ \"foo\": \"bar\" }"));
		System.out.println(parse("{ \"foo\": \"bar\", \"bob-one\": 42 }"));
	}
}
