/*linecounter.c*/
/*----------------------------------------------------------------------------*/
/* include file                                                               */
/*----------------------------------------------------------------------------*/
//#include "StdAfx.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "count_cmn.h"
#include "count_line.h"

/*----------------------------------------------------------------------------*/
/* define                                                                     */
/*----------------------------------------------------------------------------*/
//#define TESTFILE "D:\\work\\test.c"

/*----------------------------------------------------------------------------*/
/* structure definition                                                       */
/*----------------------------------------------------------------------------*/


/*----------------------------------------------------------------------------*/
/* global variable declaration                                                */
/*----------------------------------------------------------------------------*/
_LINECOUNT  gtCount;

/*----------------------------------------------------------------------------*/
/* static variable declaration                                                */
/*----------------------------------------------------------------------------*/
static _LINECOL  stCol;
static _COMMENT  stComment;

static int siStatus;		/*行ステータス*/
static int siStrStatus;	/*文字ステータス*/
static int siCommentIndex;	/*ブロックコメントのインデックス*/

static char buff[BUFF_LINE_SIZE];

/*----------------------------------------------------------------------------*/
/* static function declaration                                                */
/*----------------------------------------------------------------------------*/
static int seachLine();
static int getStatus(char **ppcurrent);
static void countCols(char *currentp, char *oldp);
static void countCol();
static void countLine();
static int isCommentStart(char *curentp);
static int seachCommentEnd(char **ppcurent);
static int seachStringKey(char **ppcurent);
static int isStringKey(char *pcurent);


/*----------------------------------------------------------------------------*/
/* global function                                                            */
/*----------------------------------------------------------------------------*/
/*コメントの設定*/
int initDefaultSetComment()
{
	int iRet;
	_COMMENT tComment;
	
	iRet = RET_OK;
	
	memset(&tComment, 0x00, sizeof(_COMMENT));

	/*コメント数*/
	tComment.iNum = COMMENT_DEFAULTNUM;

	/*ラインコメント*/
	tComment.stCmnt[0].iType = COMMENT_TYPE_LINE;
	strcpy(tComment.stCmnt[0].cCmntS, COMMENT_LINE);

	/*ブロックコメント*/
	tComment.stCmnt[1].iType = COMMENT_TYPE_BLOCK;
	strcpy(tComment.stCmnt[1].cCmntS, COMMENT_BLOCKSTART);
	strcpy(tComment.stCmnt[1].cCmntE, COMMENT_BLOCKEND);
	
	setComment(&tComment);
	
	return iRet;	
	
}

/*コメントの設定*/
int setComment(_COMMENT* ptComment)
{
	int iRet;
	
	iRet = RET_OK;
	
	memcpy(&stComment, ptComment, sizeof(_COMMENT));
	
	return iRet;
}


/*カウント*/
int getLineCount(FILE *fin) {
    int iRet;/*返却値*/

    /*初期化*/
    iRet = RET_NG;
    memset(&gtCount, 0x00, sizeof(_LINECOUNT));

    /*引数確認*/
    if(fin == NULL) {
        goto FUNC_END;
    }

	/*静的変数の初期化*/
    siStatus = LINE_STATUS_INIT;
    siStrStatus = STR_STATUS_INIT;
    siCommentIndex = COMMENT_INDEX_NONE;

    /*一行取得*/
    while(fgets(buff, BUFF_LINE_SIZE, fin) != NULL){
        /*一文字ずつチェックする*/
        seachLine();
        /*ライン数カウント*/
        countLine();
    }

    iRet = RET_OK;

FUNC_END:

    return iRet;
}

/*----------------------------------------------------------------------------*/
/* static function                                                            */
/*----------------------------------------------------------------------------*/

/*ラインの状態検索*/
static int seachLine(){
    char *currentp, *oldp;
    
    /*文字数初期化*/
	memset(&stCol, 0x00, sizeof(_LINECOL));
    
    /*buffの末尾の改行文字を取り除いておく*/
    while(strlen(buff) > 0){
    	if(IS_BREAK_LINE(&buff[strlen(buff) - 1])){
    		buff[strlen(buff) - 1] = '\0';
    	}else{
    		break;
    	}
    }    
    
    currentp = buff;
    /*一文字ずつチェックする*/
    while(!IS_NULL_CHAR(currentp)){
        /*空白省略*/
        if(IS_WHITE_SPACE(currentp) || IS_BREAK_LINE(currentp)){
            currentp++;
            continue;
        }
        
        /*現在ポインタを覚えておく*/
        oldp = currentp;
        
		/*文字列状態確認*/
        if(siStrStatus != STR_STATUS_INIT){
			/*文字列終了検索*/
        	seachStringKey(&currentp);
        	
        	/*文字数カウント*/
        	countCols(currentp, oldp);
        	
        	/*次の文字へ*/
        	currentp++;
        	continue;
        }
        
        /*TODO 文字数カウントがちゅうとはんぱ?*/
        
        /*行ステータス確認*/
        switch(siStatus){
        case LINE_STATUS_PREPRO:   /*FALLTHROUGH*/
        case LINE_STATUS_INIT:
            /*実行行*/
            getStatus(&currentp);
            /*次の文字へ*/        
            currentp++;
	       	/*文字数カウント*/
    	   	countCols(currentp, oldp);
            break;
        case LINE_STATUS_BLOCKCOMMENT:
            /*ブロックコメント終了検索*/
			seachCommentEnd(&currentp);
        	stCol.iCmntCol += currentp - oldp;
            break;
        case LINE_STATUS_LINECOMMENT:
        default:
        	/*行末尾(NULL文字前)まで移動*/
        	currentp += strlen(currentp);
        	stCol.iCmntCol += currentp - oldp;
            break;
        }

    }/*END while*/
    
    return RET_OK;
}

/*コメント,プリプロ行開始検索*/
static int getStatus(char **ppcurrent)
{
	int iRet;
    char *p;
    p = *ppcurrent;
    
	iRet = RET_FALSE;
    
    if(isCommentStart(p)){
        /*コメント文字の末尾まで移動*/
    	p += strlen(stComment.stCmnt[siCommentIndex].cCmntS) - 1;
        /*コメント行開始*/
		iRet = RET_TRUE;
        goto FUNC_END;
    }else if(IS_PREPRO(p)
                && (stCol.iCol + stCol.iPreCol) == 0){
        /*コメントを除いた行頭に#がある場合のみ*/
        /*プリプロ行開始*/
        siStatus = LINE_STATUS_PREPRO;
		iRet = RET_TRUE;
        
        /*インクルード開始か?*/
        /*プリプロ開始文字の次の文字を見る(空白省略)*/
        p++;
        SKIP_WHITE_SPACE(&p);
        if(!IS_NULL_CHAR(p)){
            if(strncmp(p, PREPRO_NAME_INCLUDE, strlen(PREPRO_NAME_INCLUDE)) == 0){
            	/*include<>かどうかを探す*/
            	p += strlen(PREPRO_NAME_INCLUDE);
		        SKIP_WHITE_SPACE(&p);
		        if((*p) == '<'){
	                siStrStatus = STR_STATUS_INCLUDE;
		        }else if((*p) == '\"'){
	                siStrStatus = STR_STATUS_STRING;		        	
		        }
		        /*'はありえない*/
            }
        }
        goto FUNC_END;
    }
    
    /*文字列開始か？*/
    if(isStringKey(p)){
    	iRet = RET_TRUE;
    }
    
FUNC_END:
	*ppcurrent= p;
	
    return RET_OK;
}

/*文字数かうんと*/
static void countCols(char *currentp, char *oldp)
{
	char *p;

	p = oldp;
	
	while(p <= currentp){
		countCol();
		p++;
	}
	
	return;
}

/*文字数かうんと*/
static void countCol()
{
    /*文字数カウント*/
    switch(siStatus){
    case LINE_STATUS_LINECOMMENT:
        /*ラインコメント*/
        stCol.iCmntCol++;
        break;
    case LINE_STATUS_BLOCKCOMMENT:
        stCol.iCmntCol++;
        break;
    case LINE_STATUS_PREPRO:
        /*プリプロ行*/
        stCol.iPreCol++;
        break;
    case LINE_STATUS_INIT:   /*FALLTHROUGH*/
    default:
        /*実行行*/
        stCol.iCol++;
        break;
    }
}

/*ライン数かうんと*/
static void countLine()
{	
#if CNT_DEBUG
//    printf("col = %d, pre = %d, comment = %d\n",stCol.iCol, stCol.iPreCol, stCol.iCmntCol);
#endif
    /*ライン数カウント*/
    if(stCol.iCol > 0){
        /*実行行*/
        gtCount.iLine++;
    }else if(stCol.iPreCol > 0 || (siStatus == LINE_STATUS_PREPRO)){
        /*プリプロ行*/
        gtCount.iPre++;
        
        if(siStatus == LINE_STATUS_PREPRO){
            /*末尾に'\'が存在する場合、プリプロ行継続*/
            if(strlen(buff) > 0){	            
	            if(IS_BACKSLASH(&buff[strlen(buff) - 1])){
	                /*プリプロ行継続*/
	            }else{
	                /*プリプロ行終了*/
	                siStatus = LINE_STATUS_INIT;
	            }
            }else{
	            /*プリプロ行終了*/
	            siStatus = LINE_STATUS_INIT;
            }
        }
    }else if(stCol.iCmntCol > 0 || (siStatus == LINE_STATUS_BLOCKCOMMENT)){
        /*コメント行*/
        gtCount.iCmnt++;
    }else{
        /*空行*/
        gtCount.iBlank++;
    }

    /*ラインコメント終了*/
    if(siStatus == LINE_STATUS_LINECOMMENT){
        siStatus = LINE_STATUS_INIT;
    }

    
    return;
}

/*現在ポインタから指定したコメント文字が開始するか？*/
static int isCommentStart(char *currentp)
{
    int iRet;
    char *p;
    unsigned int iLen, iCommentLen;
    int i;
    
    iRet = RET_FALSE;
    siCommentIndex = COMMENT_INDEX_NONE;
    p = currentp;
    iLen = 0;
    iCommentLen = 0;
    
    if(currentp == NULL){
        goto FUNC_END;
    }
    
    if(IS_NULL_CHAR(p)){
        goto FUNC_END;
    }
    
    for(i = 0; i < stComment.iNum; i++){
	    /*長さ比較*/
	    iLen = strlen(p);
	    iCommentLen = strlen(stComment.stCmnt[i].cCmntS);
	    if(iLen == 0 || iLen < iCommentLen){
	        continue;
	    }
	    /*文字比較*/
	    if(strncmp(p, stComment.stCmnt[i].cCmntS, iCommentLen) == 0){
	    	switch(stComment.stCmnt[i].iType){
	    	case COMMENT_TYPE_BLOCK:
	    		siStatus = LINE_STATUS_BLOCKCOMMENT;
	    		break;
	    	case COMMENT_TYPE_LINE:
	    	default:
	    		siStatus = LINE_STATUS_LINECOMMENT;    		
	    		break;
	    	}
	    	/*コメント文字のインデックスを設定*/
	   		siCommentIndex = i;	    	
	        iRet = RET_TRUE;
	        break;
	    }
    }

FUNC_END:
    return iRet;
}


/*現在ポインタから指定したコメント文字が開始するか？*/
static int seachCommentEnd(char **ppcurrent)
{
    int iRet;
    char *p, *oldp;
    
    iRet = RET_FALSE;
    p = *ppcurrent;
    oldp = p;
    
    /*コメント終了文字検索*/
	p = strstr(p, stComment.stCmnt[siCommentIndex].cCmntE);
	
	if(p == NULL){
        /*見つからない場合、末尾までポインタ移動*/
        *ppcurrent += strlen(*ppcurrent);
		goto FUNC_END;
	}
    
	/*現在ポインタをコメント文字の数だけ移動*/
	p += strlen(stComment.stCmnt[siCommentIndex].cCmntE);
	*ppcurrent += p - oldp;
	siStatus = LINE_STATUS_INIT;

	iRet = RET_TRUE;
    
FUNC_END:
    return iRet;
}

/*文字列終了位置を検索する*/
static int seachStringKey(char **pcurrent)
{
    int iRet;
    char *p;
    
    iRet = RET_FALSE;
    p = *pcurrent;
    
    while(!IS_NULL_CHAR(p)){
    	if(isStringKey(p)){
    		iRet = RET_TRUE;
    		break;
    	}
    	p++;
    }
    
    *pcurrent = p;
    
    return iRet;
}

/*文字列開始かどうかを判断する*/
static int isStringKey(char *pcurent)
{
    int iRet;
    char *p;
    int before;
    
    iRet = RET_FALSE;
    p = pcurent;
    
   	/*ひとつ前の文字を設定*/
    if(p == buff){
    	/*行頭の場合、ひとつ前には改行文字をいれておく*/
    	before = '\n';	
    }else{
    	before = *(p - 1);
    }
    
    /*INCLUDE固有時は終了文字を探す*/
    if(siStrStatus == STR_STATUS_INCLUDE){
    	if(*p == '>'){
            siStrStatus = STR_STATUS_INIT;
    		iRet = RET_TRUE;	
    	}
    	goto FUNC_END;
    }
    
    /*文字列開始/終了検索*/          
    if(siStrStatus != STR_STATUS_CHAR
            && *p == '\"' && before != '\\'){
        if(siStrStatus == STR_STATUS_INIT){
            /*文字列開始*/
            siStrStatus = STR_STATUS_STRING;
        }else{
            /*文字列終了*/
            siStrStatus = STR_STATUS_INIT;
        }
        iRet = RET_TRUE;
        goto FUNC_END;
    }
    
    if(siStrStatus != STR_STATUS_STRING
         && *p == '\'' && before != '\\'){
        if(siStrStatus == STR_STATUS_INIT){
            /*文字列開始*/
            siStrStatus = STR_STATUS_CHAR;
        }else{
            /*文字列終了*/
            siStrStatus = STR_STATUS_INIT;
        }
        iRet = RET_TRUE;
        goto FUNC_END;
    }
        
FUNC_END:
    return iRet;
}

