//######################################################
//
//                    わけわけ。
//                Ver.1.01 by x@rgs
//
//#####################################################

#include<sys/stat.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<locale.h>
#include<ctype.h>

#if ((defined WIN32)||(defined WIN64))
	#define UNICODE
	#define _UNICODE

	typedef	wchar_t	TCHAR;
	#define	__TEXT(q)	L##q
	#define _tmain wmain

	#define	_tprintf	wprintf
	#define	_stprintf	swprintf
	#define _tfopen		_wfopen
	#define	_ftprintf	fwprintf
	#define	_totlower	towlower
	#define _tcstold	wcstold
	#define _tstati64	_stati64
	#define	_tcslen		wcslen
	#define	_tcsrchr	wcsrchr
	#define	_tcsncpy	wcsncpy
	#define _tsetlocale _wsetlocale
#else
	typedef char	TCHAR;
	#define	__TEXT(q)	q
	#define _tmain main

	#define	_tprintf	printf
	#define	_stprintf	sprintf
	#define _tfopen		fopen
	#define _ftprintf	fprintf
	#define	_totlower	tolower
	#define _tcstold	strtold
	#define _tstati64	stat64
	#define _fstati64	fstat64
	#define	_tcslen		strlen
	#define	_tcsrchr	strrchr
	#define	_tcsncpy	strncpy
	#define _tsetlocale setlocale
#endif

#define	_T(x)		__TEXT(x)

#ifdef __cplusplus
	extern "C"
#endif
void __wgetmainargs(int*,wchar_t***,wchar_t***,int,int*);

const long split_buffer_size=(1<<20)*5;
const long split_max_part=999+26*26*26;

struct option{
	TCHAR size[128];
};


//分割ファイル名の拡張子を作成
TCHAR* create_part_ext(long count,int ext_length=3);


//分割ファイル名の拡張子を作成
TCHAR* create_part_ext(long count,int ext_length){
	//'static'注意
	static TCHAR ext[256];

	int maxnumber=0;
	long maxalphabet=1;

	//'999'や'99999'など数字のみで構成される拡張子のmaxを作成
	for(int ii=0;ii<ext_length;ii++){
		int temp=9;
		for(int iii=0;iii<ii;iii++)temp*=10;
		maxnumber+=temp;
	}

	//アルファベットのみで構成される拡張子の総数を計算
	for(int iii=0;iii<ext_length;iii++)maxalphabet*=26;

	if(count<=maxnumber){
		_stprintf(ext,_T("%0*ld"),ext_length,count);
	}else if(count<=maxnumber+maxalphabet){
		long i;

		ext[ext_length]=_T('\0');
		i=count-(maxnumber+1);
		for(long ii=ext_length-1;;ii--){
			ext[ii]=i%26+_T('a');
			if(ii>0)i/=26;
			else break;
		}
	}
	return ext;
}

//ファイルを分割する(分割サイズまたは分割数指定による)
//分割サイズは1024b、10k、100mの様に指定する(10kb、100mbでも可)
//分割数は6,12の様に単位を付加せず指定する
bool splitfile(const TCHAR* target_file_name,const TCHAR* split_opt){
	FILE *fpr,*fpw;
	TCHAR output_file_name[FILENAME_MAX];
	TCHAR* buffer;
	long long splitsize=0;
	long long* splitsizes=NULL;
	long long chunks=0;

	if(target_file_name==NULL||split_opt==NULL){
		return false;
	}

	if(!(fpr=_tfopen(target_file_name,_T("rb")))){
		_ftprintf(stderr,_T("分割対象ファイル '%s' を開くことが出来ませんでした。\n"),target_file_name);
		return false;
	}

	{
		//'k'or'kb'
		TCHAR c=_totlower(split_opt[_tcslen(split_opt)-1]),cc=_totlower(split_opt[_tcslen(split_opt)-2]);
		long double input_size=_tcstold(split_opt,NULL);

		if(c==_T('k')||cc==_T('k')){
			splitsize=input_size*1024;
		}else if(c==_T('m')||cc==_T('m')){
			splitsize=input_size*1024*1024;
		}else if(c==_T('g')||cc==_T('g')){
			splitsize=input_size*1024*1024*1024;
		}else if(c==_T('b')){
			splitsize=input_size;
		}else{
			chunks=splitsize=input_size;
		}
	}

	if(splitsize<1){
		_ftprintf(stderr,_T("分割サイズまたは分割数が正しくありません。\n"));
		return false;
	}

	long long src_file_size;
	{
		struct _tstati64 st;
		_fstati64(fileno(fpr),&st);
		src_file_size=st.st_size;
	}

	if(src_file_size<=splitsize){
		_ftprintf(stderr,_T("分割する必要がありません。\n"));
		return false;
	}

	if(chunks>split_max_part||
	   (chunks==0&&src_file_size/splitsize>split_max_part)){
		_ftprintf(stderr,_T("分割数が多すぎます。\n"));
		return false;
	}

	if(chunks>0){
		splitsizes=new long long[chunks];

		for(int ii=0;ii<chunks;ii++){
			//小数点以下を切り捨てた数値を配列に代入
			splitsizes[ii]=src_file_size/chunks;
		}

		if(src_file_size%chunks!=0){
			//割り切れなければ、先頭のファイルから1バイトずつファイルサイズを増やす
			for(int i=0;i<chunks;i++){
				splitsizes[i]++;
				long long total=0;
				for(int ii=0;ii<chunks;ii++){
					total+=splitsizes[ii];
				}
				//割り切れればループを抜ける
				if(src_file_size%total==0)break;
			}
		}
	}

	buffer=new TCHAR[split_buffer_size];
	if(buffer==NULL){
		_ftprintf(stderr,_T("メモリの確保に失敗しました。\n"));
		return false;
	}


	long long totalwrite=0;

	for(long i=0;;i++){
		long long dest_file_size=0,readsize=0,writesize=0;

		_stprintf(output_file_name,_T("%s.%s"),target_file_name,create_part_ext(i+1));

		if(!(fpw=_tfopen(output_file_name,_T("wb")))){
			_ftprintf(stderr,_T("分割ファイルの作成に失敗しました。\n"));
			return false;
		}

		_tprintf(_T("%s\n"),output_file_name);

		if(chunks>0)splitsize=splitsizes[i];

		for(dest_file_size=0,readsize=split_buffer_size;dest_file_size<splitsize;dest_file_size+=readsize){
			if(readsize>splitsize-dest_file_size)readsize=splitsize-dest_file_size;

			writesize=fread(buffer,1,readsize,fpr);
			totalwrite+=writesize;
			if(writesize<=0)break;

			fwrite(buffer,1,writesize,fpw);
		}
		if(writesize<=0||totalwrite==src_file_size)break;
	}
	if(chunks)delete[] splitsizes;
	delete[] buffer;
	return true;
}

//分割ファイルの先頭ファイルかどうか
//拡張子が000、001、0000、00001、000000など
bool is_splitfile(const TCHAR* target_file_name,int* pext_length){
	bool result=false;
	int ext_length=0,dot_position=0;

	{
		const TCHAR* tmp=_tcsrchr(target_file_name,_T('.'));
		if(!tmp)return false;
		dot_position=tmp-target_file_name;
	}

	if(dot_position!=-1){
		TCHAR ext[256]={0};

		_tcsncpy(ext,target_file_name+dot_position+1,_tcslen(target_file_name)+1);
		if(ext[_tcslen(ext)-1]==_T('0')||ext[_tcslen(ext)-1]==_T('1')){
			//拡張子末尾が'0'か'1'であれば
			ext_length++;
			if(_tcslen(ext)==1){
				//'.0'の様に一桁である場合
				result=true;
			}else{
				for(int i=2;i<=_tcslen(ext);i++){
					if(ext[_tcslen(ext)-i]==_T('0')){
						ext_length++;
						result=true;
					}else{
						//'0'以外の数字があるため先頭ファイルではない
						result=false;
						ext_length=0;
						break;
					}
				}
			}
		}
	}
	if(pext_length)*(pext_length)=ext_length;
	return result;
}

//ファイルを結合する
bool joinfile(const TCHAR* target_file_name,int ext_length){
	FILE *fpr,*fpw;
	TCHAR src_file_name[FILENAME_MAX]={0},dest_file_name[FILENAME_MAX]={0};
	TCHAR* buffer;

	{
		const TCHAR* tmp=_tcsrchr(target_file_name,_T('.'));
		if(!tmp)return false;
		int dot_position=tmp-target_file_name;

		if(target_file_name==NULL||dot_position==-1){
			return false;
		}

		_tcsncpy(dest_file_name,target_file_name,dot_position);
	}

	if(!(fpw=_tfopen(dest_file_name,_T("wb")))){
		_ftprintf(stderr,_T("結合ファイルの作成に失敗しました。\n"));
		return false;
	}

	buffer=new TCHAR[split_buffer_size];
	if(buffer==NULL){
		_ftprintf(stderr,_T("メモリの確保に失敗しました。\n"));
		return false;
	}

	for(long i=0;;i++){
		long long dest_file_size=0,writesize=0;

		_stprintf(src_file_name,_T("%s.%s"),dest_file_name,create_part_ext(i,ext_length));

		if(!(fpr=_tfopen(src_file_name,_T("rb")))){
			//拡張子'00...0'が存在しない場合以外は分割ファイル終端とみなす
			if(i!=0)break;
			else continue;
		}

		while((writesize=fread(buffer,1,split_buffer_size,fpr))){
			fwrite(buffer,1,writesize,fpw);
			dest_file_size+=writesize;
		}
	}

	_tprintf(_T("%s\n"),dest_file_name);

	delete[] buffer;

	return true;
}

int _tmain(int argc,TCHAR* argv[]){
	_tsetlocale(LC_ALL,_T("Japanese"));

	if(argc==1){
		_tprintf(_T("splilt:  wakewake -s<分割サイズ[k|m|g][b]> <対象ファイル...>\n")
				 _T("         wakewake -s<分割数> <対象ファイル...>\n")
				 _T("join  :  wakewake <対象ファイル...>\n"));
		return EXIT_FAILURE;
	}

	TCHAR *p,*pp;
	bool end_scan=false;
	option opt={0};

	while(--argc){
		++argv;
		if(!end_scan&&**argv==_T('-')){
			if(**argv==*((*argv)+1)){
				//解析終了(--)
				end_scan=true;
				continue;
			}

			++(pp=++(p=*argv));
			switch(*p){
				case _T('s'):
					//サイズ指定
					if(*pp){
						_tcsncpy(opt.size,pp,sizeof(opt.size)/sizeof(TCHAR));
					}
					break;
			}
		}else{
			p=*argv;

			if(*opt.size){
				//分割を行う
				splitfile(p,opt.size);
			}else{
				//結合を行う
				int ext_length=0;

				if(is_splitfile(p,&ext_length)){
					joinfile(p,ext_length);
				}else{
					_ftprintf(stderr,_T("'%s' は分割されたファイルではありません。\n"),p);
				}
			}
		}
	}
	return 0;
}

#if ((defined WIN32)||(defined WIN64))
int main(){
	wchar_t** argv;
	wchar_t** enpv;
	int argc=0,si=0;

	__wgetmainargs(&argc,&argv,&enpv,1,&si);
	return wmain(argc,argv);
}
#endif
