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

import java.io.Serializable;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.ranides.assira.collection.iterators.ForwardIterator;
import net.ranides.assira.text.StringUtils;


/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public abstract class AnsiPattern implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    public Iterable<AnsiMatch> find(final AnsiCharSequence input) {
        return () -> iterator(input);
    }
    
    public boolean matches(AnsiCharSequence input) {
        return find(input).iterator().hasNext();
    }
    
    public boolean apply(AnsiCharSequence input, AnsiAttr attribute) {
        boolean ret = false;
        for(AnsiMatch item : find(input)) {
            ret = true;
            if( item.groups()>0 ) {
                for(int i=1; i<=item.groups(); i++) {
                    AnsiStrings.apply(item.group(i), attribute);
                }
            } else {
                AnsiStrings.apply(item.value(), attribute);
            }
        }
        return ret;
    }
    
    protected abstract Iterator<AnsiMatch> iterator(AnsiCharSequence input);
    
    public static class REPattern extends AnsiPattern implements Serializable {
        
        private static final long serialVersionUID = 1L;
        
        private final Pattern regex;
        
        public REPattern(String regex) {
            this.regex = Pattern.compile(regex);
        }
        
        public REPattern(String regex, int flags) {
            this.regex = Pattern.compile(regex, flags);
        }

        public REPattern(Pattern regex) {
            this.regex = regex;
        }
        
        @Override
        public Iterator<AnsiMatch> iterator(final AnsiCharSequence input) {
            return new RIterator(input, regex);
        }
        
        public String pattern() {
            return this.regex.pattern();
        }
        
        public int flags() {
            return this.regex.flags();
        }
            
    }
    
    public static class MSPattern extends AnsiPattern implements Serializable {
        
        private static final long serialVersionUID = 1L;
        
        private final String pattern;

        public MSPattern(String pattern) {
            this.pattern = pattern;
        }
        
        @Override
        public Iterator<AnsiMatch> iterator(final AnsiCharSequence input) {
            return new SIterator(input, pattern);
        }
        
        public String pattern() {
            return pattern;
        }
            
    }
    
    private static class SIterator extends ForwardIterator<AnsiMatch> {

        private final AnsiCharSequence input; 
        private final String pattern;
        private int offset = -1;

        public SIterator(AnsiCharSequence input, String pattern) {
            this.input = input;
            this.pattern = pattern;
        }

        @Override
        protected boolean next(Consumer<? super AnsiMatch> action) {
            offset = StringUtils.indexOf(input, pattern, offset+1);
            if(offset>=0) {
                action.accept(new AnsiMatch(input, offset, offset+pattern.length()));
                return true;
            } else {
                return false;
            }
        }

    }
    
    private static class RIterator extends ForwardIterator<AnsiMatch> {
        
        private final AnsiCharSequence input; 
        private final Matcher matcher;

        public RIterator(AnsiCharSequence input, Pattern pattern) {
            this.input = input;
            this.matcher = pattern.matcher(input);
        }

        @Override
        protected boolean next(Consumer<? super AnsiMatch> action) {
            if( matcher.find() ) {
                action.accept(new AnsiMatch(input, matcher));
                return true;
            } else {
                return false;
            }
        }
        
    }
    
    
}
