const {parsingErrorCode, SQLParsingError} = require('./error');
const {getIndexPos} = require('./utils');

// symbols that need no spaces around them:
const compressors = '.,;:()[]=<>+-*/|!?@#';

////////////////////////////////////////////
// Parses and minimizes a PostgreSQL script.
function minify(sql, options) {

    if (typeof sql !== 'string') {
        throw new TypeError('Input SQL must be a text string.');
    }

    if (!sql.length) {
        return '';
    }

    sql = sql.replace(/\r\n/g, '\n');

    options = options || {};

    let idx = 0, // current index
        result = '', // resulting sql
        space = false; // add a space on the next step

    const len = sql.length;

    do {
        const s = sql[idx], // current symbol;
            s1 = sql[idx + 1]; // next symbol;

        if (isGap(s)) {
            while (++idx < len && isGap(sql[idx])) ;
            if (idx < len) {
                space = true;
            }
            idx--;
            continue;
        }

        if (s === '-' && s1 === '-') {
            const lb = sql.indexOf('\n', idx + 2);
            if (lb < 0) {
                break;
            }
            idx = lb - 1;
            skipGaps();
            continue;
        }

        if (s === '/' && s1 === '*') {
            let c = idx + 1, open = 0, close = 0, lastOpen, lastClose;
            while (++c < len - 1 && close <= open) {
                if (sql[c] === '/' && sql[c + 1] === '*') {
                    lastOpen = c;
                    open++;
                    c++;
                } else {
                    if (sql[c] === '*' && sql[c + 1] === '/') {
                        lastClose = c;
                        close++;
                        c++;
                    }
                }
            }
            if (close <= open) {
                idx = lastOpen;
                throwError(parsingErrorCode.unclosedMLC);
            }
            if (sql[idx + 2] === '!' && !options.removeAll) {
                if (options.compress) {
                    space = false;
                }
                addSpace();
                result += sql.substring(idx, lastClose + 2)
                    .replace(/\n/g, '\r\n');
            }
            idx = lastClose + 1;
            skipGaps();
            continue;
        }

        let closeIdx, text;

        if (s === '"') {
            closeIdx = sql.indexOf('"', idx + 1);
            if (closeIdx < 0) {
                throwError(parsingErrorCode.unclosedQI);
            }
            text = sql.substring(idx, closeIdx + 1);
            if (text.indexOf('\n') > 0) {
                throwError(parsingErrorCode.multiLineQI);
            }
            if (options.compress) {
                space = false;
            }
            addSpace();
            result += text;
            idx = closeIdx;
            skipGaps();
            continue;
        }

        if (s === `'`) {
            closeIdx = idx;
            do {
                closeIdx = sql.indexOf(`'`, closeIdx + 1);
                if (closeIdx > 0) {
                    let i = closeIdx;
                    while (sql[--i] === '\\') ;
                    if ((closeIdx - i) % 2) {
                        let step = closeIdx;
                        while (++step < len && sql[step] === `'`) ;
                        if ((step - closeIdx) % 2) {
                            closeIdx = step - 1;
                            break;
                        }
                        closeIdx = step === len ? -1 : step;
                    }
                }
            } while (closeIdx > 0);
            if (closeIdx < 0) {
                throwError(parsingErrorCode.unclosedText);
            }
            if (options.compress) {
                space = false;
            }
            addSpace();
            text = sql.substring(idx, closeIdx + 1);
            const hasLB = text.indexOf('\n') > 0;
            if (hasLB) {
                text = text.split('\n').map(m => {
                    return m.replace(/^\s+|\s+$/g, '');
                }).join('\\n');
            }
            const hasTabs = text.indexOf('\t') > 0;
            if (hasLB || hasTabs) {
                const prev = idx ? sql[idx - 1] : '';
                if (prev !== 'E' && prev !== 'e') {
                    const r = result ? result[result.length - 1] : '';
                    if (r && r !== ' ' && compressors.indexOf(r) < 0) {
                        result += ' ';
                    }
                    result += 'E';
                }
                if (hasTabs) {
                    text = text.replace(/\t/g, '\\t');
                }
            }
            result += text;
            idx = closeIdx;
            skipGaps();
            continue;
        }

        if (options.compress && compressors.indexOf(s) >= 0) {
            space = false;
            skipGaps();
        }

        addSpace();
        result += s;

    } while (++idx < len);

    return result;

    function skipGaps() {
        if (options.compress) {
            while (idx < len - 1 && isGap(sql[idx + 1]) && idx++) ;
        }
    }

    function addSpace() {
        if (space) {
            if (result.length) {
                result += ' ';
            }
            space = false;
        }
    }

    function throwError(code) {
        const position = getIndexPos(sql, idx);
        throw new SQLParsingError(code, position);
    }
}

////////////////////////////////////
// Identifies a gap / empty symbol.
function isGap(s) {
    return s === ' ' || s === '\t' || s === '\r' || s === '\n';
}

module.exports = minify;
