/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


/**
 * @author Afranio Martins<afranioce@gmail.com.br>
 * Usando como exemplo DBAL QueryBuilder Doctrine 2.0
 * 
 */
var QueryBuilder = function(connection){
    /* The query types. */
    this.SELECT = 0;
    this.DELETE = 1;
    this.UPDATE = 2;
    this.INSERT = 3;

    /** The builder states. */
    this.STATE_DIRTY = 0;
    this.STATE_CLEAN = 1;

    this.EQ       = '=';
    this.NEQ      = '<>';
    this.LT       = '<';
    this.LTE      = '<=';
    this.GT       = '>';
    this.GTE      = '>=';
    this.IN       = 'IN';
    this.NOT_IN   = 'NOT IN';
    this.LIKE     = 'LIKE';
    this.NOT_LIKE = 'NOT LIKE';
    
    /**
     * Constant that represents an AND composite expression
     */
    this.TYPE_AND = 'AND';
    
    /**
     * Constant that represents an OR composite expression
     */
    this.TYPE_OR  = 'OR';
    
    var _sql;
    var _type = this.SELECT;
    var _state = this.STATE_CLEAN;
    var _firstResult = null;
    var _maxResults = null;
    var _result = null;
    var _db;
    var _sqlParts = {
        'select'  : [],
        'from'    : [],
        'join'    : [],
        'set'     : [],
        'where'   : null,
        'groupBy' : [],
        'having'  : null,
        'orderBy' : [],
        'field'   : {
            'fields' : [],
            'values' : []
        }
    };
    var _operators = [
        this.EQ,
        this.NEQ,
        this.LT,
        this.LTE,
        this.GT,
        this.GTE,
        this.IN,
        this.NOT_IN,
        this.LIKE,
        this.NOT_LIKE
    ];


    this.getConnection = function(){
        return connection;
    }

    this.getState = function(){
        return _state;
    }

    this.execute = function(){
        var database = this.getConnection() +'.sqlite';
        var file = Titanium.Filesystem.getFile(
            Titanium.Filesystem.applicationSupportDirectory
            + '/db/' + database
        );

        //Install Database
        if(!file.exists()) {
            Ti.Database.install(database, 'db');
        }

        //Open DB
        _db = Ti.Database.open('db');

        _result = _db.execute(this.getSQL());
        return this;
    }

    this.fetchAll = function(){
        if(_.isNull(_result))
            return this;

        var rows = [];
        var index = 0;

        while (_result.isValidRow()){
            var row = {};
            row[_result.getFieldName(index)] = _result.field(index);

            rows.push(row);
            index++;
            _result.next();
        }

        //Close result set
        _result.close();
        //Close database
        _db.close();

        return rows;
    }

    this.getSQL = function(){
        if (_sql !== null && _state === this.STATE_CLEAN) {
            return _sql;
        }

        _sql = '';

        switch (_type) {
            case this.DELETE:
                _sql = getSQLForDelete();
                break;

            case this.UPDATE:
                _sql = getSQLForUpdate();
                break;

            case this.INSERT:
                _sql = getSQLForInsert();
                break;

            case this.SELECT:
            default:
                _sql = getSQLForSelect();
                break;
        }

        _state = this.STATE_CLEAN;
        _sql = _sql;

        return _sql;
    }

    this.setFirstResult = function(firstResult){
        _state = this.STATE_DIRTY;
        _firstResult = firstResult;
        return this;
    }

    this.getFirstResult = function(){
        return _firstResult;
    }

    this.setMaxResults = function(maxResults){
        _state = this.STATE_DIRTY;
        _maxResults = maxResults;
        return this;
    }

    this.getMaxResults = function (){
        return _maxResults;
    }

    this.add = function(sqlPartName, sqlPart, append){
        if(_.isUndefined(append)) append = false;

        var isObject = _.isObject(sqlPart);
        var isMultiple = _.isObject(_sqlParts[sqlPartName]);

        if (isMultiple && !isObject) {
            sqlPart = [sqlPart];
        }

        _state = this.STATE_DIRTY;
        if (append) {
            if(/field/i.exec(sqlPartName)){
                _.each(_.keys(sqlPart), function(key){
                    _sqlParts[sqlPartName][key] = _.union(_sqlParts[sqlPartName][key], sqlPart[key]);
                });
            } else if(/orderBy|groupBy|select|set|where|having/i.exec(sqlPartName)){
                if(/where|having/i.exec(sqlPartName))
                    _sqlParts[sqlPartName] = _.union(_.toArray(_sqlParts[sqlPartName]), [sqlPart]);
                else
                    _sqlParts[sqlPartName] = _.union(_.toArray(_sqlParts[sqlPartName]), _.toArray(sqlPart));
            } else if (isObject && _.isObject(sqlPart[_.keys(sqlPart)])) {
                var key = _.keys(sqlPart);
                _sqlParts[sqlPartName][key] = _.union(_.toArray(_sqlParts[sqlPartName][key]), [sqlPart[key]]);
            } else if (isMultiple) {
                _.extend(_sqlParts[sqlPartName], [sqlPart]);
            } else {
                _.extend(_sqlParts[sqlPartName], [sqlPart]);
            }

            return this;
        }

        _sqlParts[sqlPartName] = sqlPart;

        return this;
    }

    this.select = function(select){
        _type = this.SELECT;

        if (_.isUndefined(select)) {
            select = ['*'];
        }

        var selects = _.isArray(select) ? select : arguments;

        return this.add('select', selects, false);
    }

    this.addSelect = function(select){
        _type = this.SELECT;

        if (select.length == 0) {
            return this;
        }

        var selects = _.isArray(select) ? select : arguments;

        return this.add('select', selects, true);
    }

    this.delete = function(del, alias){
        _type = this.DELETE;

        if (!del) {
            return this;
        }

        return this.add('from', {
            'table': del,
            'alias': alias
        });
    }

    this.update = function(update, alias){
        _type = this.UPDATE;

        if (!update) {
            return this;
        }

        return this.add('from', {
            'table': update,
            'alias': alias
        });
    }

    this.insert = function(insert){
        _type = this.INSERT;

        if (!insert) {
            return this;
        }

        return this.add('from', {
            'table': insert
        });
    }

    this.from = function(from, alias){
        return this.add('from', {
            'table': from,
            'alias': alias
        }, true);
    }

    this.join = function(fromAlias, join, alias, condition){
        if(_.isUndefined(condition)) condition = null;

        return this.innerJoin(fromAlias, join, alias, condition);
    }

    this.innerJoin = function(fromAlias, join, alias, condition){
        if(_.isUndefined(condition)) condition = null;

        var obj = {};
        obj[fromAlias] = {
            'joinType': 'inner',
            'joinTable': join,
            'joinAlias': alias,
            'joinCondition': condition
        };

        return this.add('join', obj, true);
    }

    this.leftJoin = function(fromAlias, join, alias, condition){
        if(_.isUndefined(condition)) condition = null;

        var obj = {};
        obj[fromAlias] = {
            'joinType': 'left',
            'joinTable': join,
            'joinAlias': alias,
            'joinCondition': condition
        };

        return this.add('join', obj, true);
    }

    this.rightJoin = function(fromAlias, join, alias, condition){
        if(_.isUndefined(condition)) condition = null;
        var obj = {};
        obj[fromAlias] = {
            'joinType': 'right',
            'joinTable': join,
            'joinAlias': alias,
            'joinCondition': condition
        };

        return this.add('join', obj, true);
    }

    this.set = function(key, value){
        return this.add('set', key + ' = ' + value, true);
    }

    this.where = function(field, value, operator){
        return this.condition('where', null, field, value, operator);
    }

    this.andWhere = function(field, value, operator){
        return this.condition('where', this.TYPE_AND, field, value, operator);
    }

    this.orWhere = function(field, value, operator){
        return this.condition('where', this.TYPE_OR, field, value, operator);
    }

    this.isNull = function(field){
        return this.condition('where', this.TYPE_OR, field, null, 'IS NULL');
    }

    this.isNotNull = function(field){
        return this.condition('where', this.TYPE_OR, field, null, 'IS NOT NULL');
    }

    this.condition = function(condition, type, field, value, operator){
        value = !_.isNull(value) ? value : '';
        if (_.isArray(value)) {
            operator = !_.isUndefined(operator) ? operator : this.IN;
            operator = operator.toUpperCase().trim();
            var con = [this.IN, this.NOT_IN];

            operator = con[_.indexOf(con, operator)];
            value = "('" + implode("', '", value) + "')";
        }
        operator = !_.isUndefined(operator) && _.indexOf(_operators, operator) ? operator : this.EQ;

        var parts = field + ' ' + operator + ' ' + value;

        return this.add(condition, {
            'type': !_.isNull(type) ? type : '',
            'parts': parts.trim()
        }, true);
    }

    this.groupBy = function(group){
        if (group.length == 0) {
            return this;
        }

        var groups = _.isArray(group) ? group : arguments;

        return this.add('groupBy', groups, false);
    }

    this.addGroupBy = function(group){
        if (group.length == 0) {
            return this;
        }

        var groups = _.isArray(group) ? group : arguments;

        return this.add('groupBy', groups, true);
    }

    this.having = function(field, value, operator){
        return this.condition('having', '', field, value, operator);
    }

    this.andHaving = function(field, value, operator){
        return this.condition('having', this.TYPE_AND, field, value, operator);
    }

    this.orHaving = function(field, value, operator){
        return this.condition('having', this.TYPE_OR, field, value, operator);
    }

    this.orderBy = function(sort, order){
        return this.add('orderBy', sort + ' ' + (!order ? 'ASC' : order), false);
    }

    this.addOrderBy = function(sort, order){
        return this.add('orderBy', sort + ' ' + (!order ? 'ASC' : order), true);
    }
    
    this.field = function(field, value){
        var fields = {};
        fields[field] = value;
        return this.fields(fields);
    }
    
    this.fields = function(fields){
        if(!_.isObject(fields)){
            return this;
        }

        return this.add('field', {
            'fields': _.keys(fields),
            'values': _.values(fields)
        }, true);
    }

    this.getQueryPart = function(queryPartName){
        return _sqlParts[queryPartName];
    }

    this.getQueryParts = function(){
        return _sqlParts;
    }

    this.resetQueryParts = function(queryPartNames){
        if (_.isUndefined(queryPartNames)) {
            queryPartNames = key(_sqlParts);
        }

        for (var queryPartName in queryPartNames) {
            this.resetQueryPart(queryPartNames[queryPartName]);
        }

        return this;
    }

    this.resetQueryPart = function(queryPartName){
        _sqlParts[queryPartName] = _.isObject(_sqlParts[queryPartName])
        ? {} : null;

        _state = this.STATE_DIRTY;

        return this;
    }

    var getSQLForSelect = function(){
        var query = 'SELECT ' + implode(', ', _sqlParts['select']) + ' FROM ';

        var fromClauses = {};

        // Loop through all FROM clauses
        _.each(_sqlParts['from'], function(from){
            var fromClause = from['table'] + ' ' + from['alias'];

            if (!_.isUndefined(_sqlParts['join'][from['alias']])) {
                _.each(_sqlParts['join'][from['alias']], function(join){
                    fromClause += ' ' + join['joinType'].toUpperCase()
                    + ' JOIN ' + join['joinTable'] + ' ' + join['joinAlias']
                    + ' ON ' + (join['joinCondition']);
                })
            }
            fromClauses[from['alias']] = fromClause;

        })

        // loop through all JOIN clauses for validation purpose
        _.each(_sqlParts['join'], function(fromAlias, joins){
            if (_.isEmpty(fromClauses[fromAlias])) {
                Ti.API.Error('Error: ' + fromAlias + ' >>> ' + key(fromClauses));
            }
        });

        query += implode(', ', _(fromClauses).values())
        + (!_.isNull(_sqlParts['where']) ? ' WHERE' + doCondition('where') : '')
        + (!_.isEmpty(_sqlParts['groupBy']) ? ' GROUP BY '+ implode(', ', _sqlParts['groupBy']) : '')
        + (!_.isNull(_sqlParts['having']) ? ' HAVING' + doCondition('having') : '')
        + (!_.isEmpty(_sqlParts['orderBy']) ? 'ORDER BY ' + implode(', ', _sqlParts['orderBy']) : '')
        return (_.isNull(_maxResults) && _.isNull(_firstResult))
        ? query
        : doOffset(query, _maxResults, _firstResult);
    }

    var doCondition = function(condition){
        var conditions = '';
        _.each(_sqlParts[condition], function(cond){
            conditions += cond['type'] + ' (' + cond['parts'] + ') ';
        });
        return conditions;
    }

    var doOffset = function(query, limit, offset){
        if (!_.isNull(limit)) {
            query += ' LIMIT ' + parseInt(limit, 10);
        }

        if (offset !== null) {
            var offset = parseInt(offset, 10);
            if (offset < 0) {
                Ti.API.error("Error: LIMIT argument offset=offset is not valid");
            }
            query += ' OFFSET ' + offset;
        }

        return query;
    }

    /**
     * Converts this instance into an UPDATE string in SQL.
     *
     * @return string
     */
    var getSQLForUpdate = function(){
        var table = _sqlParts['from']['table']
        + (_sqlParts['from']['alias'] ? ' ' + _sqlParts['from']['alias'] : '');
        var query = 'UPDATE ' + table
        + ' SET ' + implode(', ', _sqlParts['set'])
        + (!_.isNull(_sqlParts['where']) ? ' WHERE' + doCondition('where') : '');

        return query;
    }

    this.__toString = function(){
        return this.getSQL();
    }

    /**
     * Converts this instance into a DELETE string in SQL.
     *
     * @return string
     */
    var getSQLForDelete = function (){
        var table = _sqlParts['from']['table'] + (_sqlParts['from']['alias'] ? ' ' 
            + _sqlParts['from']['alias'] : '');
        var query = 'DELETE FROM ' + table
        + (!_.isNull(_sqlParts['where']) ? ' WHERE' + doCondition('where') : '');

        return query;
    }

    /**
     * Converts this instance into a INSERT string in SQL.
     *
     * @return string
     */
    var getSQLForInsert = function (){
        var query = 'INSERT INTO ' + _sqlParts['from']['table'];
        if(!_.isUndefined(_sqlParts['field']['fields']))
            query += '('+ implode(', ', _sqlParts['field']['fields']) + ") VALUES ('";

        query += implode("\', \'", _sqlParts['field']['values']) + "\')";

        return query;
    }

    var implode = function(separator, array){
        var array = _.toArray(array);
        if(!_.isEmpty(array))
            return array.length === 1 ? array[0] : array.join(separator)
    }
}

var db = new QueryBuilder('db_teste')

.select('type')
.addSelect('body', 'created', 't.tid')
.from('node', 'n')
.setFirstResult(2)
.setMaxResults(10)
.orderBy('nid', 'DESC')
.addOrderBy('tid', 'ASC')
.groupBy('tid')
.addGroupBy('nid')
.innerJoin('n', 'taxonomy_term', 't', 'n.nid = t.tid')
.leftJoin('n', 'taxonomy_term_data', 'td', 'n.nid = td.tid')
.where('n.nid', '2')
.andWhere('n.nid', '4')
.orWhere('n.nid', '5')
.andWhere('n.nid', [1, 2, 3])
.orWhere('n.nid', [1, 2, 3], 'NOT IN')
.having('t.tid','1', '=')
.andHaving('t.tid2','1', '=')
.isNull('n.nid')
.isNotNull('n.nid');
/*
.delete('sdfs')
.where('n.nid', '5')
.andWhere('n.nid', [1, 2, 3])
.orWhere('n.nid', [1, 2, 3], 'NOT IN')

.update('node', 'n')
.set('nid', 2)
.where('n.nid', '5')
.andWhere('n.nid', [1, 2, 3])
.orWhere('n.nid', [1, 2, 3], 'NOT IN')

.insert('node')
.field('nid', 1)
.field('title', 1)
.fields({
    'title': 'teste de titulo',
    'body' : '<p>sdfsdf</p>'
})
/*
db.trucate(table);
db.drop('table').exists();
db.create('node').notExists().fields({
    'nid': {
        'type': 'serial',
        'null': false,
        'description': 'sdfsdf',
    },
    'title': {
        'type': 'string',
        'length': 32,
        'null': false,
        'description': 'sdfsdf',
    }
})
.primaryKey('nid')
.uniqueKey()
.indexes()
.foreingKey()
 */
window.onload = document.write(db.getSQL());
//module.exports = QueryBuilder;