/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.text;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.ranides.assira.generic.Serializer;
import net.ranides.assira.generic.ValueUtils;
import net.ranides.assira.math.HashHelper;
import net.ranides.assira.reflection.inspect.Resolver;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class ResolveFormatter implements Serializable {
    
    private static final long serialVersionUID = 0L;
    
    private static final Pattern REGEX = Pattern.compile("\\{(.+?)\\}");
    
    private final String pattern;
    private final Token[] tokens;

    private ResolveFormatter(String pattern) {
        this.pattern = pattern;
        
        List<Token> list = new ArrayList<>();
        Matcher hit = REGEX.matcher(pattern);
        int last = 0;
        while(hit.find()) {
            if(hit.start()-last > 0) {
                list.add(new SToken(pattern.substring(last,hit.start())));
            }
            list.add(new RToken(hit.group(1)));
            last = hit.end();
        }
        if(last<pattern.length()) {
            list.add(new SToken(pattern.substring(last)));
        }
        this.tokens = list.toArray(new Token[list.size()]);
    }
    
    public static ResolveFormatter compile(String pattern) {
        return new ResolveFormatter(pattern);
    }
    
    public static Appendable formatTo(Appendable target, String pattern, Object context) throws IOException {
        return compile(pattern).formatTo(target, context);
    }
    
    public static String format(String pattern, Object context) {
        return compile(pattern).format(context);
    }
    
    public Appendable formatTo(Appendable target, Object context) throws IOException {
        for(Token token : tokens) {
            token.append(target, context);
        }
        return target;
    }
    
    public String format(Object context) {
        try {
            return formatTo(new StringBuilder(), context).toString();
        } catch (IOException ex) {
            // that should never happen
            throw new AssertionError(ex);
        }
    }
    
    public String pattern() {
        return pattern;
    }

    @Override
    public int hashCode() {
        return HashHelper.hashValues(7, 19, pattern);
    }

    @Override
    public boolean equals(Object object) {
        if(!(object instanceof ResolveFormatter)) {
            return false;
        }
        ResolveFormatter other = (ResolveFormatter)object;
        return ValueUtils.equals(this.pattern, other.pattern);
    }
    
    private Object writeReplace() {
        return Serializer.proxy(this, pattern);
    }
            
    private interface Token {
        
        void append(Appendable target, Object context) throws IOException;
        
    }
    
    private static final class SToken implements Token {
        
        private final String value;

        public SToken(String value) {
            this.value = value;
        }

        @Override
        public void append(Appendable target, Object context) throws IOException {
            target.append(value);
        }
        
    }
    
    private static final class RToken implements Token {
        
        private final Resolver resolver;

        public RToken(String expression) {
            this.resolver = Resolver.compile(expression);
        }

        @Override
        public void append(Appendable target, Object context) throws IOException {
            target.append(String.valueOf( resolver.get(context)));
        }
        
    }
    
}
