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

import java.util.LinkedList;
import java.util.List;
import net.ranides.assira.lexer.CharConsumer;
import net.ranides.assira.lexer.CharStreamer;
import net.ranides.assira.trace.LoggerUtils;
import org.apache.log4j.Logger;

/**
 *
 * @author ranides
 */
@SuppressWarnings("PMD") // sorry, interpetery ad-hoc zawsze są brzydkie
public final class StringCompiler {

    private StringCompiler() { /* utility class */ }
    
    private static final Logger LOGGER = LoggerUtils.getLogger();
    
    private enum CharsType {
        TEXT, OCT, 
    }
    
    public static String quote(String value) {
        if (null == value || value.isEmpty()) {
            return "\"\"";
        }
        
        int offset = 1, size = quoteCount(value);
        char c, result[] = new char[size];
        
        for(int i=0, n=value.length(); i<n; i++) {
            c = value.charAt(i);
            if( c=='\\' || c=='"') { result[offset++] = '\\'; }
            result[offset++] = c;
        }
        result[0] = '"';
        result[size-1] = '"';
        return new String(result);
    }
    
    private static int quoteCount(String value) {
        int size = value.length() + 2;
        char c;
        for(int i=0, n=value.length(); i<n; i++) {
            c = value.charAt(i);
            if( c=='\\' || c=='"') { size++; }
        }
        return size;
    }
    
    public static String quote(Object value) {
        if (null == value) {
            return "\"\"";
        }
        return quote(value.toString());
    }
    
    public static String[] splitQuoted(String text) {
        List<String> result = new LinkedList<>();
        boolean inquote = false;
        boolean escape = false;
        int last = 0;
        for(int i=0; i<text.length(); i++) {
            if( !escape && '\\' == text.charAt(i) ) { 
                escape = true;
                continue;
            }
            if(escape) {
                escape = false;
                continue;
            }
            if( '"' == text.charAt(i) ) { 
                inquote = !inquote; 
                continue;
            }
            if( ' ' == text.charAt(i) && !inquote ) {
                if( last-i != 0 ) {
                    result.add( unquote(text.substring(last, i)) );
                }
                last = i+1;
            }
        }
        String tail = text.substring(last);
        if(!tail.trim().isEmpty()) {
            result.add( StringCompiler.unquote(tail) );
        }
        return result.toArray(new String[result.size()]);
    }
    
/* ************************************************************************** */
    
    public static String unquote(String value) {
        String test = unquoteCheck(value);
        if(null != test) { return test; }

        // tak, obie funkcje są "brzydkie" ale kod działa
        // 5-10 razy szybciej od String#replaceAll
        // 2 razy szybciej od apache.commons.StrBuilder#replaceAll
        char[] result = new char[value.length()-2];
        return new String(result, 0, removeQuotes(result, removeSlashes(result, value) ) );
    }
    
    static String unquoteCheck(String value) {
        if (null == value || value.isEmpty() ) {
            return "";
        }
        if("\"".equals(value)) {
            throw new IllegalArgumentException("string quotes must be opened and closed but only single quote found.");
        }
        boolean as = value.charAt(0)=='"';
        boolean at = value.charAt(value.length()-1)=='"';
        if( at!=as) {
            throw new IllegalArgumentException("string quotes must be opened and closed.");
        } else {
            return !at ? value : null;
        }
    }
    
    private static int removeSlashes(char[] buffer, String source) {
        int position    = 0;
        boolean skip    = false;

        for(int i=1, length=source.length()-1; i<length; i++) {
            char recent = source.charAt(i);
            if(recent=='\\') {
                if(skip) { 
                    skip = false; 
                } else {
                    buffer[position++] = recent;
                    skip = true;
                }
            } else {
                buffer[position++] = recent;
                skip = false;
            }
        }
        return position;
    }
    
    private static int removeQuotes(char[] buffer, int length) {
        int position    = 0;
        boolean skip       = false;
        
        for(int i=0; i<length; i++) {
            char c = buffer[i];
            if(c=='"') {
                if(skip) { 
                    skip = false; 
                    buffer[position-1] = '"';
                } else {
                    buffer[position++] = c;
                    skip = true;
                }
            } else {
                buffer[position++] = c;
                skip = (c=='\\');
            }
        }
        return position;
    }

    
/* ************************************************************************** */
    
    
    public static String compile(String input) {
        Generator generator = new Generator();
        
        CharStreamer<CharsType> stream = new CharStreamer<>(generator);
        stream.open(input.toCharArray());
        
        while( !stream.end() ) {
            char c = stream.peek();
            if( c=='\\' ) {
                stream.flush(CharsType.TEXT);
                stream.skip();
                processEsc(stream);
            } else {
                stream.skip();
            }
        }
        stream.flush(CharsType.TEXT);
        stream.close();
        return generator.toString();
    }
    
    private static void processEsc(CharStreamer<CharsType> stream) {
        if( stream.end() ) {
            stream.close();
            return;
        } 
        
        char code = stream.get();
        
        if('n'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\n'});
        } 
        else if('r'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\r'});
        } 
        else if('t'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\t'});
        } 
        else if('f'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\f'});
        } 
        else if('b'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\b'});
        } 
        else if('\\'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\\'});
        } 
        else if('e'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\033'});
        } 
        else if('^'==code) {
            stream.flush();
            stream.send(CharsType.TEXT, new char[]{'\033','['});
        } 
        else if(Character.isDigit(code)) {
            processOct(stream);
        }    
    }
    
    private static boolean isOct(char ch) {
        return (ch>='0' && ch<='7');
    }
        
    private static void processOct(CharStreamer<CharsType> stream) {
        if( isOct(stream.peek()) ) { stream.skip(); } 
        if( isOct(stream.peek()) ) { stream.skip(); } 
        stream.flush(CharsType.OCT, 1,0);
    }
    
/* ************************************************************************** */
    
    private static class Generator implements CharConsumer<CharsType> {

        private final StringBuilder builder = new StringBuilder();
        
        @Override
        public void consume(CharsType type, char[] data) {
            if(type==CharsType.TEXT) {
                builder.append(data);
            } else 
            if(type==CharsType.OCT) {
                builder.append( (char)Integer.parseInt(new String(data),8) );
            } else {
                LOGGER.warn("unknown sequence: " + new String(data));
            }
        }
        
        @Override
        public String toString() {
            return builder.toString();
        }
    
    }


    
}
