#include <fstream>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

//-------------------------------------------
typedef unsigned char Bajt;

//-------------------------------------------
class NizBajtova
{
public:
	unsigned Velicina() const 
		{ return _Bajtovi.size(); }			
		
	void Isprazni()
		{ _Bajtovi.clear(); }
		
	void Dodaj( Bajt x )
		{ _Bajtovi.push_back( x ); }
		
	Bajt operator[] ( unsigned i ) const
		{ return _Bajtovi[i]; }

	void Citaj( istream& udat )
		{
		udat.seekg( 0, ios::end );
		unsigned velicina = udat.tellg();
		udat.seekg( 0, ios::beg );
		_Bajtovi.resize( velicina );
		udat.read( _Bajtovi.begin(), velicina );
		if( !udat )
			throw string( "Nije uspelo citanje!" );
		}
		
	void Pisi( ostream& idat ) const
		{
		idat.write( _Bajtovi.begin(), Velicina() );
		if( !idat )
			throw string( "Nije uspelo pisanje!" );
		}
		
private:
	vector<Bajt> _Bajtovi;
};

//-------------------------------------------
class Transformacija
{
public:
	virtual ~Transformacija()
		{}	
	virtual void Kodiranje( const NizBajtova& u, NizBajtova& i ) const = 0;
	virtual void Dekodiranje( const NizBajtova& u, NizBajtova& i ) const = 0;
	virtual Transformacija* Kopija() const = 0;
};

//-------------------------------------------
class Translacija : public Transformacija
{
public:
	Translacija( Bajt b )
		: _X(b)
		{}
	
	void Kodiranje( const NizBajtova& u, NizBajtova& i ) const
		{ Transliranje( u, i, _X ); }
	
	void Dekodiranje( const NizBajtova& u, NizBajtova& i ) const
		{ Transliranje( u, i, -_X ); }
		
	Transformacija* Kopija() const
		{ return new Translacija(_X); }
private:
	void Transliranje( const NizBajtova& u, NizBajtova& i, Bajt b ) const
		{
			i.Isprazni();
			unsigned v = u.Velicina();
			for( unsigned k=0; k<v; k++ ){
				Bajt x = u[k] + b;
				i.Dodaj( x );
			}
		}
		
	Bajt _X;
};

//-------------------------------------------
class Rotacija : public Transformacija
{
public:
	Rotacija( Bajt b )
		: _X(b)
		{}
	
	void Kodiranje( const NizBajtova& u, NizBajtova& i ) const
		{ Rotiranje( u, i, _X); }
	
	void Dekodiranje( const NizBajtova& u, NizBajtova& i ) const
		{ Rotiranje( u, i, 8 - _X); }
		
	Transformacija* Kopija() const
		{ return new Rotacija(_X); }
private:
	void Rotiranje( const NizBajtova& u, NizBajtova& i, Bajt b ) const
		{
			i.Isprazni();
			unsigned v = u.Velicina();
			for( unsigned k=0; k<v; k++ ){
				Bajt x = u[k];

				//Sledeci red ne valja. I u slucaju kodiranja i u slucaju dekodiranja 
				// radice isto tj. radice kodiranje.
				//i.Dodaj( (x << _X) | (x >> (8-_X)) );
				//Ovako treba:

				i.Dodaj( (x << b) | (x >> (8-b)) );
			
			}
		}
		
	Bajt _X;
};

//-------------------------------------------
class SlozenaTransformacija : public Transformacija
{
public:
	SlozenaTransformacija()
		{}

	~SlozenaTransformacija()
		{
		for( int i=0; i<_Transformacije.size(); i++ )
			delete _Transformacije[i];
		}

	SlozenaTransformacija( const SlozenaTransformacija& st )
		{
		for( int i=0; i<st._Transformacije.size(); i++ )
			_Transformacije.push_back( st._Transformacije[i]->Kopija() );
		}
		
	SlozenaTransformacija& operator = ( const SlozenaTransformacija& st )
		{
		if( this != &st ){
			for( int i=0; i<_Transformacije.size(); i++ )
				delete _Transformacije[i];
			_Transformacije.clear();
			for( int i=0; i<st._Transformacije.size(); i++ )
				_Transformacije.push_back( st._Transformacije[i]->Kopija() );
		}
		return *this;
		}

	Transformacija* Kopija() const
		{ return new SlozenaTransformacija(*this); }
		
	void Kodiranje( const NizBajtova& u, NizBajtova& i ) const
		{
		if( _Transformacije.size() > 0 ){
			NizBajtova t1 = u;
			NizBajtova t2;
			for( unsigned i=0; i<_Transformacije.size(); i++ ){
				_Transformacije[i]->Kodiranje( t1, t2 );
				t1 = t2;
			}
			i = t2;
		}
		else
			i = u; 
		}

	void Dekodiranje( const NizBajtova& u, NizBajtova& i ) const
		{ 
		if( _Transformacije.size() > 0 ){
			NizBajtova t1 = u;
			NizBajtova t2;
			for( int i=_Transformacije.size()-1; i>=0; i-- ){
				_Transformacije[i]->Dekodiranje( t1, t2 );
				t1 = t2;
			}
			i = t2;
		}
		else
			i = u; 
		}
		
	void Dodaj( const Transformacija* t ) 
		{ _Transformacije.push_back(t); }
		
private:
	vector<const Transformacija*> _Transformacije;
};

//-------------------------------------------
class ProgramKodDekod
{
public:
	void Priprema( int argc, char** argv )
		{
		if( argc < 4 )
			throw string("Upotreba: prg <u.dat> <i.dat> <nacin kodiranja>");
		
		_NazivUlazneDatoteke = argv[1];
		_NazivIzlazneDatoteke = argv[2];
		
		for( int i=3; i<argc; i++ ){
			string param = argv[i];
			if( param == "add" ){
				if( ++i<argc ){
					int n;
					if( !sscanf( argv[i], "%d", &n ))
						throw string("Neispravan parametar kodiranja add!");
					_Transformacija.Dodaj( new Translacija( n ));
				}else
					throw string("Nedostaje parametar kodiranja add!");				
			}else if( param == "rot" ){
				if( ++i<argc ){
					int n;
					if( !sscanf( argv[i], "%d", &n ))
						throw string("Neispravan parametar kodiranja rot!");
					_Transformacija.Dodaj( new Rotacija( n ));
				}else
					throw string("Nedostaje parametar kodiranja rot!");				
/*
			}else if( param == "xor" ){
				if( ++i<argc )
					_Transformacija.Dodaj( new XorTransformacija( argc[i] ));
				else
					throw string("Nedostaje parametar kodiranja xor!");				
			}else if( param == "xorstart" ){
				_Transformacija.Dodaj( new XorStartTransformacija );
*/			}else
				throw string("Nepoznat tip kodiranja");				
		}
		}
		
	void Obrada() const
		{
		ifstream udat( _NazivUlazneDatoteke.c_str(), ios::binary );
		if( !udat )
			throw string( "Nije uspelo otvaranje ulazne datoteke!" );
		ofstream idat( _NazivIzlazneDatoteke.c_str(), ios::binary );
		if( !idat )
			throw string( "Nije uspelo otvaranje izlazne datoteke!" );

		NizBajtova ulaz, izlaz;
		ulaz.Citaj( udat );
		Operacija( ulaz, izlaz );
		izlaz.Pisi( idat );		
		}
		
protected:
	virtual void Operacija( const NizBajtova& u, NizBajtova& i ) const = 0;
	SlozenaTransformacija	_Transformacija;
		
private:
	string 					_NazivUlazneDatoteke;
	string 					_NazivIzlazneDatoteke;
};

//-------------------------------------------
class ProgramKodiranje : public ProgramKodDekod
{
protected:
	void Operacija( const NizBajtova& u, NizBajtova& i ) const
		{ _Transformacija.Kodiranje( u, i ); }
};

//-------------------------------------------
class ProgramDekodiranje : public ProgramKodDekod
{
protected:
	void Operacija( const NizBajtova& u, NizBajtova& i ) const
		{ _Transformacija.Dekodiranje( u, i ); }
};

//-------------------------------------------
main( int argc, char** argv )
{
	try	{
		//ProgramKodiranje p;
		ProgramDekodiranje p;
		p.Priprema( argc, argv );
		p.Obrada();		
	} catch( string& s ){
		cerr << "GRESKA: " << s << endl;
	}
}

