/*
    sqlite3xx - sqlite3 C++ layer, following ideas of libpqxx
    Copyright (C) 2009  Andreas Baumann

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "port/string.h"

#include "sqlite3xx/result.hpp"

#include <iostream>
#include <sstream>
#include <stdexcept>

#include <cassert>
#include <cstdlib>

#include "sqlite3xx/except.hpp"

#include "port/unused.h"

using namespace std;

namespace sqlite3xx {

/* field */

ostream& operator<<( ostream& o, const result::field& f ) {
	int type = f._t.column_type( f._c );
	switch( type ) {
		case SQLITE_INTEGER: {
			int value = f._t.GetValueInt( f._c );
			o << value;
			}
			break;

		case SQLITE3_TEXT: {
			const unsigned char* value = f._t.GetValueText( f._c );
			o << value;
			}
			break;

		case SQLITE_FLOAT: {
			double value = f._t.GetValueDouble( f._c );
			o << value;
			}
			break;

		case SQLITE_NULL:
			o << "(NULL)";
			break;

		default:
			o << "(Unknown type " << type << "!)";
	}
	return o;
}

/* result */

void result::Step( ) {
	int rc;

	rc = sqlite3_step( _stmt );
	switch( rc ) {
		case SQLITE_DONE:
			if( _crow == 0 ) {
				/* never had a result, will never read-ahead and buffer */
				_status = st_nodata;
			} else {
				/* last row */
				_status = st_lastrow;
				_crow++;
			}
			break;

		case SQLITE_ROW:
			_status = st_hasdata;
			_crow++;
			break;

		/* don't fail if the sqlite file is locked by another writer, try again later */
		case SQLITE_BUSY:
			throw database_locked( "database locked in step" );

		default: {
			ostringstream s;
			s << "Illegal state after sqlite3_step (sqlite code: " << rc << ")";
			string str = s.str( );
			string msg = sqlite3_errmsg( sqlite3_db_handle( _stmt ) );
			throw sql_error( str, msg );
			}
	}
}

void result::BufferData( ) {
	const char *tmp = NULL;

	_cache.resize( columns( ) + 1 );

	for( size_type i = 0; i < columns( ); i++ ) {
		CachedValue &value = _cache.at( i );
		value.type = column_type( i );
		switch( value.type ) {
			case SQLITE_INTEGER:
				value.value.i = sqlite3_column_int( _stmt, i );
				break;
			
			case SQLITE3_TEXT:
				/* KLUDGE: the documentation is not specific about returning NULL here
				 * I get the feeling this is undefined behaviour, so people should not
				 * relly on it from outside.
				 */
				tmp = (const char *)sqlite3_column_text( _stmt, i );
				if( tmp != NULL ) {
					if( value.value.s != NULL ) free( value.value.s );
					value.value.s = (unsigned char *)strdup( tmp );
				} else {
					value.type = SQLITE_NULL;
					value.value.s = NULL;
				}
				break;

			case SQLITE_FLOAT:
				value.value.d = sqlite3_column_double( _stmt, i );
				break;

			case SQLITE_NULL:
				value.value.s = NULL;
				break;

			default: {
				ostringstream s;
				s << "Illegal type " << value.type << " when buffering row " << _row;
				string msg = s.str( );
				throw logic_error( msg );
			}
		}
	}
}

void result::FillColNameMap( ) {
	size_type i;
	const char* colname;
	for( i = 0; i < columns( ); i++ ) {
		colname = sqlite3_column_name( _stmt, i );
		_colmap.insert( make_pair( string( colname ), i ) );
	}
}

void result::FillColTypeMap( ) {
	size_type i;
	_coltype.resize( columns( ) + 1 );
	for( i = 0; i < columns( ); i++ ) {
		_coltype.at( i ) = sqlite3_column_type( _stmt, i );
	}
}

result::result( const result& r ) {
	_stmt = r._stmt;
	_status = r._status;
	_colmap = r._colmap;
	_coltype = r._coltype;
	_cache = r._cache;
	_row = r._row;
	_crow = r._crow;
}


result::result( sqlite3_stmt* stmt ) {
	_stmt = stmt;
	_status = st_nascent;
	_row = 0;
	_crow = 0;
	Step( );
	if( _status == st_hasdata || _status == st_lastrow ) {
		FillColNameMap( );
		FillColTypeMap( );
		BufferData( );
		Step( );
	}
}

result::~result( ) {
	switch( _status ) {
		case st_nascent: /* created no result fetched, weird, but ok */
			break;

		case st_nodata:
			/* a command without result data, just reset the prepared statement
			 * so it can be used again for the next set of parameters
			 */
			break;

		case st_lastrow:
			break;

		case st_hasdata:
		case st_nomoredata:
			/* TODO: free row table data */
			break;

		default:
			throw logic_error( "Illegal state in destructor" );
	}
}

result& result::operator=( const result& r ) {
	if( this != &r ) {
		_stmt = r._stmt;
		_status = r._status;
		_colmap = r._colmap;
		_coltype = r._coltype;
		_cache = r._cache;
		_row = r._row;
		_crow = r._crow;
	}
	return *this;
}

result::size_type result::affected_rows( ) const {
	switch( _status ) {
		case st_nascent:
			throw logic_error( "Called affected_rows() before sqlite3_step!" );

		case st_nodata:
			return sqlite3_changes( sqlite3_db_handle( _stmt ) );

		case st_hasdata:
		case st_lastrow:
		case st_nomoredata:
			throw logic_error( "Affected rows doesn't make sence in a query!" );

		default:
			throw logic_error( "Illegal state in affected_rows()" );
	}
}

result::size_type result::size( ) const {
	/* TODO: can we get this value with sqlite3_step, I doubt!
	 * at least we know from the state wheter it's 0 or 1 and more
	 */
	switch( _status ) {
		case st_nascent:
			throw logic_error( "Called size() before sqlite3_step!" );

		case st_nodata:
			return 0;

		case st_hasdata:
			/* ..but we know that the result must be at least _row */
			return _row + 2;

		case st_nomoredata:
			return _row + 1;

		case st_lastrow:
			return _row + 1;

		default:
			throw logic_error( "Illegal state in size()" );
	}
}

result::size_type result::columns( ) const {
	return sqlite3_column_count( _stmt );
}

const result::tuple result::operator[]( result::size_type i ) throw( ) {
	if( _row == i ) {
		return tuple( this, i );
	} else if( i == _row + 1 ) {
		_row++;
		BufferData( );
		Step( );
		return tuple( this, i );
	} else {
		throw logic_error( "Illegal access outside scope of cursor!" );
	}
}

result::size_type result::column_number( string name ) const {
	ColMap::const_iterator it = _colmap.find( name );
	if( it == _colmap.end( ) ) {
		throw invalid_argument( "Unknown column '" + name + "' requested" );
	}
	return it->second;
}

int result::column_type( size_type i ) const {
	if( _status != st_hasdata && _status != st_lastrow ) {
		throw logic_error( "Fetching row after sqlite returned no data!" );
	}
	return _coltype[i];
}

/* value caching */

result::CachedValue::CachedValue( const CachedValue& v ) {
	type = v.type;
	value = v.value;
	if( type == SQLITE3_TEXT ) {
		value.s = (unsigned char *)strdup( (const char *)v.value.s );
	}
}

result::CachedValue::~CachedValue( ) {
	if( type == SQLITE3_TEXT && value.s != NULL ) {
		free( value.s );
		value.s = NULL;
	}
}

result::CachedValue& result::CachedValue::operator= ( const result::CachedValue& v ) {
	if( this != &v ) {
		type = v.type;
		value = v.value;
		if( type == SQLITE3_TEXT ) {
			value.s = (unsigned char *)strdup( (const char *)v.value.s );
		}
	}
	return *this;	
}

/* const iterator for tuples */

result::const_iterator result::const_iterator::operator++( int incr ) {
	SQLITEXX_UNUSED( incr );
	const_iterator old( *this );
	// idx++;
	return old;
}

bool result::const_iterator::operator<( const result::const_iterator& i ) const {
	SQLITEXX_UNUSED( i.dummy );
	// idx < i.idx
	return false;
}

} /* namespace sqlite3xx */
