/* ////////// LICENSE INFO ////////////////////

 * Copyright (C) 2013 by NYSOL CORPORATION
 *
 * Unless you have received this program directly from NYSOL pursuant
 * to the terms of a commercial license agreement with NYSOL, then
 * this program is licensed to you under the terms of the GNU Affero General
 * Public License (AGPL) 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 EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF 
 * NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Please refer to the AGPL (http://www.gnu.org/licenses/agpl-3.0.txt)
 * for more details.

 ////////// LICENSE INFO ////////////////////*/
// ============================================================================
// kgCSV.cpp KGMODで用いられるCSVデータの入力関連の関数
// ============================================================================
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
#include <kgEnv.h>
#include <kgConfig.h>
#include <kgMethod.h>
#include <kgCSV.h>
#include <kgError.h>
#include <fcntl.h>
#include <sys/stat.h>

using namespace std;
using namespace kglib;

// ----------------------------------------------------------------------------
// 項目数が異なるerrorメッセージの表示&終了
// ----------------------------------------------------------------------------
static void fldCntErr(int fldCnt, int j) throw(kgError) {
	ostringstream ss;
	ss << "detected different number of fields (header has " << fldCnt << " but the record has " << j<< ")";
	throw kgError(ss.str());
}
// ============================================================================
// kgCSVクラス
// ============================================================================
// ----------------------------------------------------------------------------
// clean up:初期状態の戻す
// ----------------------------------------------------------------------------
void kgCSV::clear(void){
	env_			 = 0;
	fd_				 = 0;
	opened_    = false;
	ap_.clear();
	buf_			 = 0;
	maxRecLen_ = 0;
	queSize_	 = 0;
	border_    = 0;
	dupTop_    = 0;
	dupSize_   = 0;
	bufSize_   = 0;
	curPnt_    = 0;
	noFldName_ = false;
  fldName_.clear();
	fldNum_.clear();
	fldSize_     = 0;
	fldByteSize_ = 0;
	status_    	 = 0;
	recNo_     	 = 0;
}
// ----------------------------------------------------------------------------
//  データのバッファへの読み込み
//	バッファ最後のmaxRecLen_文字をbuffer先頭緩衝領域にコピー
// ----------------------------------------------------------------------------
void kgCSV::readCSVfile() throw(kgError) 
{
	if(! begin()){ memcpy(buf_, dupTop_, dupSize_); }
	curPnt_ = curPnt_ - queSize_;
	size_t maxSize=ioSize_*ioCnt_;
	size_t accSize=0;
	size_t resSize=maxSize;
	while(accSize<maxSize){
		int rsize = ::read(fd_, buf_ + dupSize_ + accSize, resSize<ioSize_ ? resSize : ioSize_);
		if( rsize < 0 ){ throw kgError(); }
		if( rsize == 0 ) { 
			//最後が改行でなければ改行付加(ただしゼロバイトファイル除く)
			if( !( begin() && accSize==0) ){
				if (*(buf_+dupSize_+accSize-1)!='\n' 
						&& *(buf_+dupSize_+accSize-1)!='\r')
				{
					*(buf_+dupSize_+accSize)='\n';
					accSize++;
				}
			}
			break;
		}
		accSize+=static_cast<size_t>(rsize);
		resSize-=static_cast<size_t>(rsize);
	}
	*(buf_+dupSize_+accSize) = '\0';
}

// ----------------------------------------------------------------------------
// 各種変数の初期化＆ファイルオープン(実ファイルと標準入力の識別)
// queSize_ : queueのサイズ
// ioSize_ : read関数で一回に読込むサイズ
// ioCnt_ : 一回のkgIFP::readでread関数を何回呼ぶか(= queSize_/ioSize_)
// ----------------------------------------------------------------------------
void kgCSV::open(const kgstr_t& fileName, kgEnv* env, bool noFldName,size_t cnt) throw(kgError) 
{
	// 初期値セット
	noFldName_   = noFldName;
	buf_         = 0;
	curPnt_      = 0;
	fldByteSize_ = 0;
	recNo_       = 0;
	env_         = env;
	status_      = Begin;
	if(fileName.size()==0){ fname_="/dev/stdin"; }
	else									{ 
		vector<kgstr_t> name(1,fileName);
		fname_=kgFilesearch(name,false,false,false).at(0); 
	}
	if(env_==NULL){
		maxRecLen_ = KG_MaxRecLen;
		ioSize_    = KG_iSize;
	}else{
		maxRecLen_ = env_->getMaxRecLen();
		ioSize_    = env_->iSize();
	}
	queSize_   = cnt * ioSize_;
	if( queSize_ % ioSize_ != 0 ) {
		throw kgError("internal error: invalid queue count");
	}
  ioCnt_   = queSize_ / ioSize_;

	// オープン処理
	try {
		fd_ = ::open(fname_.c_str(), O_RDONLY);
		if(fd_ == -1 ){ throw kgError(); }
	}catch(kgError& err){
		ostringstream ss;
		ss << "file read open error: " << fname_;
		throw kgError(ss.str());
	}
	opened_=true;
}

//------------------------------------------------------------------------------
// バッファの確保＆項目行の読み込み
//------------------------------------------------------------------------------
void kgCSV::set_fields(size_t dupSize) 
{
	//バッファ確保
	dupSize_ = dupSize;
	bufSize_ = dupSize_ + queSize_;
	try {
		ap_.set( new char[bufSize_+1] );
	} catch(...) {
		throw kgError("memory allocation error on CSVin");
	}
	buf_ = ap_.get();

	// データ読込（readCSVfileで読み込まれる）
	// border_ : 読み込み限界位置、
	// dupTop_ : 緩衝領域へのコピーする先頭アドレス（バッファ末尾か１or２行分前アドレス）
	// curPnt_ : バッファ末尾+1にセット。初回のreadCSVfileのため
	border_ = buf_ + bufSize_ - maxRecLen_;
	dupTop_ = buf_ + bufSize_ - dupSize_;
	curPnt_ = buf_+bufSize_;
	readCSVfile();

	// いきなりのeofの検知:
	// -nfnで0バイトファイルの場合はなにもせずreturn
	// -nfnでなく0バイトファイルの場合はエラー
	if(isEOF()){
		status_=End;
		if(noFldName_){
  		fldSize_=0;
			return;
		}else{
			ostringstream ss;
			ss << "no data found : " << fname_;
			throw kgError(ss.str());
		}
	}

	// 項目名(先頭行)の取得
	// 項目名無しの場合は番号がセットされる
	if(!noFldName_){
  	fldSize_=kglib::cntFldToken(curPnt_, maxRecLen_);
		kgAutoPtr2<char*> nameChr_ap;
		nameChr_ap.set( new char*[fldSize_] );
		char** nameChr = nameChr_ap.get();
		char*  endStr=sepFldToken(nameChr, fldSize_, curPnt_, maxRecLen_);
  	for(size_t i=0; i<fldSize_; i++){
			string s=*(nameChr+i);
			fldName_.push_back( s );
			fldNum_[s]=i;
		}
		fldByteSize_=endStr - buf_ - dupSize_ + 1;
		curPnt_ = endStr + 1;
	}else{ 
  	fldSize_=kglib::cntFldToken(curPnt_, maxRecLen_);
		fldByteSize_=0;
	}
}
// ----------------------------------------------------------------------------
// 読み込みファイルのクローズ
//  ファイル入力の場合(stdinでない場合)にclose処理
// ----------------------------------------------------------------------------
void kgCSV::close(void) throw(kgError) 
{
	if(!opened_) return;
	try {
		::close(fd_);
	}catch(kgError& err){
		ostringstream ss;
		ss << "file read close error: " << fname_;
		throw kgError(ss.str());
	}
	opened_=false;
}
// ----------------------------------------------------------------------------
// num番目の項目名を返す
// ----------------------------------------------------------------------------
kgstr_t kgCSV::fldName(const size_t num) const throw(kgError) 
{
	if(num < fldSize_){
		return fldName_[num];
	}else{
		ostringstream ss;
		ss << "field number " << num+1 <<  " is out of range";
		throw kgError(ss.str());
	}
}
// ----------------------------------------------------------------------------
// 項目名から番号を返す。
// 項目名が見つからない時は rtnがtrueなら-1 rtnがfalseならエラー デフォルトはfalse
// ----------------------------------------------------------------------------
int kgCSV::fldNum(const kgstr_t& str,bool rtn) const throw(kgError) 
{
	map<kgstr_t,int>::const_iterator i = fldNum_.find(str);
	if( i != fldNum_.end() ){
		return i->second;
	}else{
		if(rtn){ return -1;}
		else{
			ostringstream ss;
			ss << "field name not found: `" << str << "' in "<< fname_;
			throw kgError(ss.str());
		}
	}
}
// ----------------------------------------------------------------------------
// バッファ内容の丸ごとコピー
// toEndがtrueならばカレントポインタに関係なく全バッファをコピー
// falseならばカレントポインタまでをコピーする
// コピーしたバイト数を返す。
// rubyのmtableで利用している.
// ----------------------------------------------------------------------------
size_t kgCSV::bufCpy(char* to, bool toEnd) const
{
	if(toEnd) memcpy(to, buf_, bufSize_ + 1    );
	else      memcpy(to, buf_, curPnt_ - buf_ + 1);
	return to - buf_;
}
// ----------------------------------------------------------------------------
// ファイルがパイプかどうかのチェック
// ----------------------------------------------------------------------------
bool kgCSV::isFifo(void) const
{
	struct stat st;
	if(fname_.empty()){ return false;}
	if(::stat(fname_.c_str(), &st)==-1){ return false;}
	return S_ISFIFO(st.st_mode);
}
// ----------------------------------------------------------------------------
// データ部の先頭の位置へ戻す処理
// 標準入力あるいはパイプの場合はエラーとする
// ----------------------------------------------------------------------------
void kgCSV::seekTop(void)throw(kgError) 
{
	// 先頭シーク
	try {
		::lseek(fd_,0,0);       
	}catch(...){
		ostringstream ss;
		ss << "input file seek error: " << fname_;
		throw kgError(ss.str());
	}

	// バッファ再読込
	status_ = Begin;
	recNo_=0;
	curPnt_ = buf_ + bufSize_;
	readCSVfile();

	// いきなりのeofの検知:
	// -nfnで0バイトファイルの場合はなにもせずreturn
	// -nfnでなく0バイトファイルの場合はエラー
	if(isEOF()){
		status_=End;
		if(noFldName_){
			fldSize_=0;
			return;
		}else{
			ostringstream ss;
			ss << "no data found: " << fname_;
			throw kgError(ss.str());
		}
	}

	// 項目名がある場合は項目行をスキップ
	if(!noFldName_){ 
		curPnt_ =  buf_ + dupSize_ + fldByteSize_;
		if(isEOF()) status_ = End;
	}
}
// ============================================================================
// kgCSVrec
// ============================================================================
// -----------------------------------------------------------------------------
// バッファ確保＆ヘッダ読み込み
// -----------------------------------------------------------------------------
void kgCSVrec::read_header(void)
{
	_rec=NULL;
	if(!opened_) return;
	set_fields(maxRecLen_);
	if(isEOF()){ status_ = End; }
}
// -----------------------------------------------------------------------------
// 読み込み
//  _recに現在読込データ位置がセットされる
// -----------------------------------------------------------------------------
int kgCSVrec::read() 
{
	// データ読込
	if( end() ) return EOF;
	if(isEndOfBuf()) readCSVfile();

	// データセット
	_rec = curPnt_;
	curPnt_ = sepRecToken(curPnt_, maxRecLen_) + 1;
	recNo_++;

	// statusセット
	if(isEOF()) status_ = End;
	else        status_ = Normal;
	return 1;
}
// ============================================================================
// kgCSVfld
// ============================================================================
// -----------------------------------------------------------------------------
// バッファ確保＆ヘッダ読み込み
// -----------------------------------------------------------------------------
void kgCSVfld::read_header(void)
{
	if(!opened_) return;
	set_fields(maxRecLen_);
	_fld_ap.set( new char*[fldSize_] );
	_fld    = _fld_ap.get();
	if(isEOF()) status_ = End;
}
// -----------------------------------------------------------------------------
// 読み込み
//  _fldに現在読込データ位置がセットされる(分割された各項目へのポインタ)
// -----------------------------------------------------------------------------
int kgCSVfld::read(void) 
{
	// データ読込
	if( end() ) return EOF;
	if(isEndOfBuf()) readCSVfile();

	// 項目分割	&データセット
	curPnt_ = sepFldToken(_fld, fldSize_, curPnt_, maxRecLen_) + 1;
	recNo_++;

	// statusセット
	if(isEOF()) status_ = End;
	else        status_ = Normal;
	return 1;
}
// -----------------------------------------------------------------------------
// 読み込み(一つのバッファ制限を超えたらEOFを返す)
// ruby のmtableで利用している。
// -----------------------------------------------------------------------------
int kgCSVfld::read_limit(void) 
{
	// データ読込
	if(end()) return EOF;
	if(isEndOfBuf()) return -9;

	// 項目分割	&データセット
	curPnt_ = sepFldToken(_fld, fldSize_, curPnt_, maxRecLen_) + 1;
	recNo_++;

	// statusセット
	if(isEOF()) status_ = End;
	else        status_ = Normal;
	return 1;
}
// ============================================================================
// kgCSVkey
// ============================================================================
// -----------------------------------------------------------------------------
// バッファ確保＆ヘッダ読み込み
// -----------------------------------------------------------------------------
void kgCSVkey::read_header(void) 
{
	if(!opened_) return;
	set_fields(maxRecLen_*2);
	if(isEOF()){
		status_=KeyBreak;
		status_|=End;
	}
	_newFld = NULL;
	_oldFld = NULL;
	_fld0_ap.set( new char*[fldSize_] );
	_fld1_ap.set( new char*[fldSize_] );
	_fld0 = _fld0_ap.get();
	_fld1 = _fld1_ap.get();
	for(size_t i=0; i<fldSize_; i++){
		_fld0[i]=const_cast<char*>(""); 
		_fld1[i]=const_cast<char*>(""); 
	}
	_newFld   = _fld0;
	_oldFld   = _fld1;
}
// -----------------------------------------------------------------------------
// 読み込み
//  _newFld,_oldFldに現在読込データ位置がセットされる(分割された各項目へのポインタ)
// -----------------------------------------------------------------------------
int kgCSVkey::read() 
{
	if( end() ) return EOF;
	if(isEndOfBuf()){
		readCSVfile();
		for(size_t i=0; i<fldSize_; i++){ _newFld[i] -= queSize_; }
	}

	// newとoldを入れ換える(次にnewに新しい行の項目位置を代入)
	char **tmpFld=_newFld;
	      _newFld=_oldFld;
	      _oldFld= tmpFld;

	// ファイル終了時はnew行にダミーセット
	if(isEOF()){
		status_=KeyBreak;
		status_|=End;
		for(size_t i=0; i<fldSize_; i++){
			_newFld[i]=const_cast<char*>(""); // NULL;
		}
		return 1;
	}

	// 項目分割	
	curPnt_ = sepFldToken(_newFld, fldSize_, curPnt_, maxRecLen_) + 1;
	recNo_++;

	// statusセット
	// 最初の行の読み込み時はBegin
	// 二行目以降の読み込み時はNormal or KeyBreak
	if( recNo_==1 ){ 
		status_=Begin;
	}else{               
		char *newKey;
		char *oldKey;
		status_=Normal;
		for(unsigned int i=0; i<_keyNum.size(); i++){
			newKey=getNewVal(_keyNum.at(i));
			oldKey=getOldVal(_keyNum.at(i));
			if( strcmp(newKey,oldKey)!=0 ){
				status_=KeyBreak;
				break;
			}
		}
	}
	return 1;
}
// ============================================================================
// kgCSVblk
// ============================================================================
// -----------------------------------------------------------------------------
// バッファ確保＆ヘッダ読み込み
//  kgCSVkeyのバッファ確保＆ヘッダ読み込み＋ kgCSVblk用のバッファを確保
// -----------------------------------------------------------------------------
void kgCSVblk::read_header(void) 
{
	kgCSVkey::read_header();
	if(!opened_) return;
	if(env_==NULL){
		_pageSize = KG_BlockCount;
	}else{
		_pageSize = env_->getBlockCount();
	}
	try {
		_fldbuf_ap.set( new kgFldBuffer(_pageSize,bufSize_+1,env_) );
		_blk_ap.set( new char*[fldSize_] );

	} catch(...) {
		throw kgError("memory allocation error on kgCSVblk");
	}
	_fldbuf = _fldbuf_ap.get();
	_blkFld = _blk_ap.get();
}

// -----------------------------------------------------------------------------
// バッファページサイズ変更(read_header使用前のみ利用可)
// -----------------------------------------------------------------------------
void kgCSVblk::setbufsize(size_t psize)
{ 
	if(_fldbuf!=0){ throw kgError("run setbufsize() after read_header()");}
	_pageSize=psize; 
}
// -----------------------------------------------------------------------------
// キーブレークまでのデータがセットされる
//	kgCSVblk::blkread()によって一行ずつ読み込みことができる
//	再読込前にデータ複製
// -----------------------------------------------------------------------------
int kgCSVblk::blkset() 
{
	// 初期設定
	_fldbuf->reset();
	if( end() ) return EOF;
	size_t s_rec=recNo_;
	_start = curPnt_;	
	if(recNo_==0)	{ s_rec = 1; }
	else					{ _start = curPnt_-recLen(); }

	while(1){
		// データ読込
		if(isEndOfBuf()){
			if( recNo_!=0 ){ _fldbuf->write(_start,curPnt_-_start-recLen()); }
			readCSVfile();
			for(size_t i=0; i<fldSize_; i++){ _newFld[i] -= queSize_; }
			_start = curPnt_-recLen();
		}

		// newとoldを入れ換える(次にnewに新しい行の項目位置を代入)
		char **tmpFld=_newFld;
		      _newFld=_oldFld;
	 		   	_oldFld= tmpFld;

		// ファイル終了時はnew行にダミーセット	
		if(isEOF()){
			status_=KeyBreak;
			status_|=End;
			for(size_t i=0; i<fldSize_; i++){
				_newFld[i]=const_cast<char*>(""); // NULL;
			}
			_end = curPnt_;
			_blkrec = recNo_ +1 - s_rec;
			break;
		}

		// 項目分割	
		curPnt_ = sepFldToken(_newFld, fldSize_, curPnt_, maxRecLen_) + 1;
		recNo_++;

	// statusセット
	// 最初の行の読み込み時はBegin
	// 二行目以降の読み込み時はNormal or KeyBreak
		if( recNo_==1 ){ 
			status_=Begin;
		}else{               
			char *newKey;
			char *oldKey;
			status_=Normal;
			for(unsigned int i=0; i<_keyNum.size(); i++){
				newKey=getNewVal(_keyNum.at(i));
				oldKey=getOldVal(_keyNum.at(i));
				if( strcmp(newKey,oldKey)!=0 ){
					status_=KeyBreak;
					break;
				}
			}
		}
		if(status_&KeyBreak){
			_end = curPnt_-recLen();
			_blkrec = recNo_ - s_rec;
			break;
		}
	}
	_pos = _start ; 
	return _blkrec;
}
// -----------------------------------------------------------------------------
// blkset()によってセットされたデータを１行ずつ読み込む
//  blkset()の際にsepFldTokenのよって項目分割されている
//  "\0"によってデータ分割されているので(','と'\n'が’\0’に置換えられる)
//  項目数分の"\0"までをよみこめばOK。
//  ただしデータにDQが存在していた時ように特殊処理をしている
//  データDQが存在していた場合sepFldTokenによって
//  "38669",38669,65,"7",50
//  38106,"""3""8106",32,4,50
//   ↓
//  "38669\0,38669\065\0"7\0,50\0
//  38106,""3"8106\0,",32\04\050\0
//  となっており,
//  開始のDQをそのままで終了DQは'\0'に置換えられる
//  (','と'\n'はそのまま残っているので読み飛ばす必要が有る)
//  さらにDQでかこまれた中にDQDQが有るとDQとして変換される 
//  そのため次に項目を読み込む際に減らしたDQ分読みとバス必要が有る
// -----------------------------------------------------------------------------
int kgCSVblk::blkread(void){

	if(_fldbuf->getFld(_blkFld,fldSize_)==EOF){

		if(_pos == _end) { return EOF;}
		if(*_pos =='"' && (_pos+1) == _end) { return EOF;}
		size_t i=0;
		bool DQflg=false;
		int DQcnt=0; 
		if(*_pos=='"'){ _pos++; DQflg=true;}

		for(char *p=_pos; p<_end && i<fldSize_ ;p++){
			if(*p=='"'){DQcnt++;}
			if(!*p){
				_blkFld[i]=_pos; 
				if(DQflg){
					if(p<_end){ p=p+1+DQcnt; }
					DQflg=false;
				}
				if(p<_end && *(p+1)=='"'&& i+1!=fldSize_){ 
					//最終項目以外次回読み込み位置セット
					DQflg=true;
					p++; 
				}
				_pos=p+1; 
				DQcnt=0;
				i++;
			}
		}
		if(i<fldSize_){ fldCntErr(fldSize_,i);}
	}
	return 1;
}
// -----------------------------------------------------------------------------
// ブロック先頭SEEK
// -----------------------------------------------------------------------------
int kgCSVblk::seekBlkTop(void){
	_pos = _start;
	return _fldbuf->seekRTop();	
}

