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

package net.ranides.assira.reflection;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.ranides.assira.collection.arrays.NativeArray;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.generic.Wrapper;
import net.ranides.assira.reflection.impl.RCompareUtils;
import net.ranides.assira.text.StringTraits;
import net.ranides.assira.text.StringUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class ResolvePattern implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private static final Pattern RE_INDEX = Pattern.compile("\\[([^\\]]+)\\]");
    
    private final ResolveStrategy handler;
    
    private final String expression;
    
    private final String[] tokens;
    
    private ResolvePattern(ResolveStrategy handler, String expression) {
        assert expression != null;
        this.handler = handler;
        String[] array = StringUtils.replace(expression, RE_INDEX, m -> "."+m.group(1)+".").split("\\.");
        this.expression = expression;
        this.tokens = Arrays.stream(array).filter(p->!p.isEmpty()).toArray(String[]::new);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(tokens);
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object == null || getClass() != object.getClass()) {
            return false;
        }
        ResolvePattern that = (ResolvePattern) object;
        return Arrays.equals(tokens, that.tokens);
    }

    @Override
    public String toString() {
        return StringUtils.join(tokens, ".");
    }
    
    public static ResolvePattern compile(String expression) {
        return new ResolvePattern(ResolveStrategy.THROW, expression);
    }
    
    public static ResolvePattern compile(ResolveStrategy handler, String expression) {
        return new ResolvePattern(handler, expression);
    }
    
    
    public String expression() {
        return expression;
    }
    
    public Object get(Object that) {
        ResolveStatus status = new ResolveStatus();
        for(int i=0,n=tokens.length-1; i<n; i++) {
            that = iget(status, that, tokens[i]);
            if(status.error) {
                return handler.get(this, tokens[i]);
            }
        }
        Object out = iget(status, that, tokens[tokens.length-1]);
        if(status.error) {
            return handler.get(this);
        }
        return out;
    }
    
    public void set(Object that, Object value) {
        ResolveStatus status = new ResolveStatus();
        for(int i=0,n=tokens.length-1; i<n; i++) {
            that = iget(status, that, tokens[i]);
            if(status.error) {
                handler.get(this, tokens[i]);
            }
        }
        iset(status, that, tokens[tokens.length-1], value);
        if(status.error) {
            handler.set(this);
        }
    }
    
    public Object replace(Object that, Object value) {
        ResolveStatus status = new ResolveStatus();
        for(int i=0,n=tokens.length-1; i<n; i++) {
            that = iget(status, that, tokens[i]);
            if(status.error) {
                handler.get(this, tokens[i]);
            }
        }
        Object out = ireplace(status, that, tokens[tokens.length-1], value);
        if(status.error) {
            return handler.replace(this);
        }
        return out;
    }
    
    public IClass<?> type(Object that) {
        ResolveStatus status = new ResolveStatus();
        IClass<?> type = IClass.typefor(that);
        for(int i=0,n=tokens.length-1; i<n; i++) {
            type = itype(status, type, tokens[i]);
            if(status.error) {
                return handler.type(this, tokens[i]);
            }
        }
        IClass<?> out = itype(status, type, tokens[tokens.length-1]);
        if(status.error) {
            return handler.type(this);
        }
        return out;
    }
    
    public Wrapper<Object> bind(Object that) {
        return new RWrapper(that);
    }
    
    private Object iget(ResolveStatus status, Object that, String name) {
        for(Model m : MODELS) {
            if(m.matches(that)) {
                return m.get(status, that, name);
            }
        }
        status.error = true;
        return null;
    }
    
    private void iset(ResolveStatus status, Object that, String name, Object value) {
        for(Model m : MODELS) {
            if(m.matches(that)) {
                m.set(status, that, name, value);
                return;
            }
        }
        status.error = true;
    }
    
    private Object ireplace(ResolveStatus status, Object that, String name, Object value) {
        for(Model m : MODELS) {
            if(m.matches(that)) {
                return m.replace(status, that, name, value);
            }
        }
        status.error = true;
        return null;
    }
    
    private IClass<?> itype(ResolveStatus status, IClass<?> that, String name) {
        for(Model m : MODELS) {
            if(m.matchesType(that)) {
                return m.type(status, that, name);
            }
        }
        status.error = true;
        return null;
    }
    
    private Object writeReplace() {
        return SerializationUtils.proxy(this, handler, expression);
    }
    
    private static final Model[] MODELS = Model.values();
    
    
    private enum Model {
        ARRAY(){
            @Override
            public boolean matches(Object that) {
                return that!=null && that.getClass().isArray();
            }
            @Override
            public boolean matchesType(IClass<?> that) {
                return IAttribute.ARRAY.matches(that);
            }
            @Override
            public Object get(ResolveStatus _s, Object that, String name) {
                return NativeArray.wrap(that).get(Integer.parseInt(name));
            }
            @Override
            public void set(ResolveStatus _s, Object that, String name, Object value) {
                NativeArray.wrap(that).set(Integer.parseInt(name), value);
            }
            @Override
            public Object replace(ResolveStatus _s, Object that, String name, Object value) {
                return NativeArray.wrap(that).replace(Integer.parseInt(name), value);
            }
            @Override
            public IClass<?> type(ResolveStatus _s, IClass<?> that, String name) {
                return that.component();
            }
        },
        LIST(){
            @Override
            public boolean matches(Object that) {
                return that instanceof List<?>;
            }
            @Override
            public boolean matchesType(IClass<?> that) {
                return that!=IClass.NULL && RCompareUtils.isRawSuper(IClass.typeinfo(List.class), that);
            }
            @Override
            public Object get(ResolveStatus _s, Object that, String name) {
                return ((List<?>)that).get(Integer.parseInt(name));
            }
            @Override
            @SuppressWarnings("unchecked")
            public void set(ResolveStatus _s, Object that, String name, Object value) {
                ((List<Object>)that).set(Integer.parseInt(name), value);
            }
            @Override
            @SuppressWarnings("unchecked")
            public Object replace(ResolveStatus _s, Object that, String name, Object value) {
                List<Object> list = (List<Object>)that;
                int index = Integer.parseInt(name);
                Object prv = list.get(index);
                list.set(index, value);
                return prv;
            }
            @Override
            public IClass<?> type(ResolveStatus _s, IClass<?> that, String name) {
                return that.params().get(0);
            }
        },
        MAP(){
            @Override
            public boolean matches(Object that) {
                return that instanceof Map<?,?>;
            }
            @Override
            public boolean matchesType(IClass<?> that) {
                return that!=IClass.NULL && RCompareUtils.isRawSuper(IClass.typeinfo(Map.class), that);
            }
            @Override
            public Object get(ResolveStatus _s, Object that, String name) {
                return ((Map<?,?>)that).get(name);
            }
            @Override
            @SuppressWarnings("unchecked")
            public void set(ResolveStatus _s, Object that, String name, Object value) {
                ((Map<String,Object>)that).put(name, value);
            }
            @Override
            @SuppressWarnings("unchecked")
            public Object replace(ResolveStatus _s, Object that, String name, Object value) {
                return ((Map<String,Object>)that).put(name, value);
            }
            @Override
            public IClass<?> type(ResolveStatus _s, IClass<?> that, String name) {
                return that.params().get(1);
            }
        },
        MATCHER(){
            @Override
            public boolean matches(Object that) {
                return that instanceof Matcher;
            }
            @Override
            public boolean matchesType(IClass<?> that) {
                return that!=IClass.NULL && IClass.typeinfo(Matcher.class).isSuper(that);
            }
            @Override
            public Object get(ResolveStatus _s, Object that, String name) {
                if(StringTraits.isNumber(name)) {
                    return ((Matcher)that).group(Integer.parseInt(name));
                } else {
                    return ((Matcher)that).group(name);
                }
            }
            @Override
            public void set(ResolveStatus _s, Object that, String name, Object value) {
                throw new UnsupportedOperationException("Matcher is immutable.");
            }
            @Override
            public Object replace(ResolveStatus _s, Object that, String name, Object value) {
                throw new UnsupportedOperationException("Matcher is immutable.");
            }
            @Override
            public IClass<?> type(ResolveStatus _s, IClass<?> that, String name) {
                return IClass.typeinfo(String.class);
            }
        },
        CHARS(){
            @Override
            public boolean matches(Object that) {
                return that instanceof CharSequence;
            }
            @Override
            public boolean matchesType(IClass<?> that) {
                return that!=IClass.NULL && IClass.typeinfo(CharSequence.class).isSuper(that);
            }
            @Override
            public Object get(ResolveStatus _s, Object that, String name) {
                return ((CharSequence)that).charAt(Integer.parseInt(name));
            }
            @Override
            public void set(ResolveStatus _s, Object that, String name, Object value) {
                throw new UnsupportedOperationException("CharSequence is immutable.");
            }
            @Override
            public Object replace(ResolveStatus _s, Object that, String name, Object value) {
                throw new UnsupportedOperationException("CharSequence is immutable.");
            }
            @Override
            public IClass<?> type(ResolveStatus _s, IClass<?> that, String name) {
                return IClass.typeinfo(char.class);
            }
        },
        BEAN(){
            @Override
            public boolean matches(Object that) {
                return that != null;
            }
            @Override
            public boolean matchesType(IClass<?> that) {
                return that!=IClass.NULL;
            }
            @Override
            public Object get(ResolveStatus status, Object that, String name) {
                BeanModel model = BeanModel.typefor(that);
                if(model.properties().containsKey(name)) {
                    return model.property(name).get().get(that);
                } 
                Iterator<IField> f = field(model, name);
                if(f.hasNext()) {
                    return f.next().get(that);
                }
                status.error = true;
                return null;
            }

            private Iterator<IField> field(BeanModel model, String name) {
                return model.type().fields().require(IAttribute.ANY).require(name).iterator();
            }
            
            @Override
            public void set(ResolveStatus status, Object that, String name, Object value) {
                BeanModel model = BeanModel.typefor(that);
                if(model.properties().containsKey(name)) {
                    model.property(name).get().set(that, value);
                    return;
                }
                Iterator<IField> f = field(model, name);
                if(f.hasNext()) {
                    f.next().set(that, value);
                    return;
                }
                status.error = true;
            }
            @Override
            public Object replace(ResolveStatus status, Object that, String name, Object value) {
                BeanModel model = BeanModel.typefor(that);
                if(model.properties().containsKey(name)) {
                    return model.property(name).get().replace(that, value);
                }
                Iterator<IField> f = field(model, name);
                if(f.hasNext()) {
                    return f.next().replace(that, value);
                }
                status.error = true;
                return null;
            }
            @Override
            public IClass<?> type(ResolveStatus status, IClass<?> that, String name) {
                BeanModel model = BeanModel.typeinfo(that);
                if(model.properties().containsKey(name)) {
                    return model.property(name).get().type();
                }
                Iterator<IField> f = field(model, name);
                if(f.hasNext()) {
                    return f.next().type();
                }
                status.error = true;
                return null;
            }
        };
        
        public abstract boolean matchesType(IClass<?> that);
        
        public abstract boolean matches(Object that);
        
        public abstract Object get(ResolveStatus status, Object that, String name);
        
        public abstract void set(ResolveStatus status, Object that, String name, Object value);
        
        public abstract Object replace(ResolveStatus status, Object that, String name, Object value);
        
        public abstract IClass<?> type(ResolveStatus status, IClass<?> that, String name);
        
    }
    
    private class RWrapper implements Wrapper<Object> {
        
        private final Object that;

        public RWrapper(Object that) {
            this.that = that;
        }

        @Override
        public Object get() {
            return ResolvePattern.this.get(that);
        }

        @Override
        public void set(Object value) {
            ResolvePattern.this.set(that, value);
        }

        @Override
        public Object replace(Object value) {
            return ResolvePattern.this.replace(that, value);
        }
        
        @Override
        public IClass<?> type() {
            return ResolvePattern.this.type(that);
        }
    
    }
    
    private static final class ResolveStatus {
        
        public boolean error;
        
    }
    
    
}
