#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <list>
#include <string>
#include <functional>

//
//	Describes one single puzzle.
//
class puzzle
{
protected:
	int width;
	int height;
	typedef std::vector<char> content_type;
	content_type content;
	enum symbols {
		empty = 0,
		wall,
		box,
		goal,
		box_in_goal,
		worker
	};

public:
	puzzle()
		: width(0)
		, height(0)
	{}

	bool load( const std::string& name )
	{
		//
		//	Open file.
		//
		std::cout << "Loading " << name << "..." << std::flush; 
		std::ifstream is( name );
		if ( !is )
		{
			std::cout << "file not found." << std::endl;
			return false;
		}
		//
		//	Read line by line and calculate width and hight.
		//
		std::string line;
		while ( std::getline( is, line ) )
		{
			width = std::max( width, (int)line.length() );
			++height;
		}
		//
		//	Encode puzzle.
		//
		is.clear();
		is.seekg(0);
		std::cout << "encoding..." << std::flush; 
		content.resize(width*height);
		content_type::iterator it = content.begin();
		while ( std::getline( is, line ) )
		{
			for ( char c : line )
			{
				switch ( c )
				{
				case ' ':
					*it++ = empty;
					break;
				case '#':
					*it++ = wall;
					break;
				case '$':
					*it++ = box;
					break;
				case '.':
					*it++ = goal;
					break;
				case '*':
					*it++ = box_in_goal;
					break;
				case '@':
					*it++ = worker;
					break;
				default:
					throw std::runtime_error( 
							std::string("Unexpected symbol '" )+c+"' found." );
				}
			}
			for ( int i=0; i < width - line.length(); ++i)
				*it++ = empty;
		}
		std::cout << "done" << std::endl;
		return true;	
	}

	int size()
	{
		return 2+width*height;
	}

	void encode( std::ostream& os )
	{
		if ( width > height )
			encode_as_is(os);
		else
			encode_rotated(os);
	}

private:
	
	void encode_as_is( std::ostream& os )
	{
		os << (char)width;
		os << (char)height;
		for ( char c : content )
			os << c;
	}
	
	void encode_rotated( std::ostream& os )
	{
		std::cout << "rotating." << std::endl; 
		os << (char)height;
		os << (char)width;
		for ( int i = 0; i < width; ++i )
			for ( int j = 0; j < height; ++j )
				os << content[ j*width+i ];
	}

};

//
//	Describes catalog of puzzles.
//
class catalog
{
protected:
	typedef std::list<puzzle*> puzzles_type;
	puzzles_type puzzles;
	
	typedef int i32;
	typedef i32 offset_type;
	typedef i32 count_type;

public:

	~catalog()
	{
		for ( auto p : puzzles )
			delete p;
	}

	void push( puzzle * p )
	{
		puzzles.push_back( p );
	}

	void compile( std::ostream& os )
	{
		std::cout << "Compiling." << std::endl; 
		encode( os,(count_type)puzzles.size());
		offset_type bgn = sizeof(count_type)+puzzles.size()*sizeof(offset_type);
		std::cout << "Table ... "; 
		for ( puzzle * p : puzzles )
		{
			encode(os,bgn);
			bgn += p->size();
		}
		std::cout << "done." << std::endl;
		std::cout << "Puzzles...";
		for ( puzzle * p : puzzles )
		{
			p->encode(os);
		}
		std::cout << "done." << std::endl;
	}

protected:
	template <class I>
	void encode( std::ostream& os, I i)
	{
		if ( sizeof(i) == 1 )
			os << (char)i;
		else if ( sizeof(i) == 2 )
		{
			os << (char)(i&0xff);
			os << (char)(i>>8&0xff);
		}
		else if ( sizeof(i) == 4 )
		{
			os << (char)(i&0xff);
			os << (char)(i>>8&0xff);
			os << (char)(i>>16&0xff);
			os << (char)(i>>24&0xff);
		}
	}
};

int main( void )
try
{
	catalog c;

	//
	//	Compile all screen_xxx.sokoban files where xxx is the level.
	//
	for ( int i = 1; true; ++i )
	{	
		std::ostringstream ss;
		ss << "screen_" << i << ".sokoban";
		puzzle * p = new puzzle;
		if ( !p->load( ss.str()) )
		{
			delete p;
			break;
		}
		c.push( p );
	}

	std::ofstream os( "puzzles.bin", std::ios::binary );
	c.compile( os );
	return 0;
}
catch ( const std::exception& e )
{
	std::cout << "std::exception: " << e.what() << std::endl;
}
catch ( ... )
{
	std::cout << "unknown exception." << std::endl;
}


