/*
 * Copyright 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "wfc_util_log.h"

/*
static void wfc_util_printf(char *pSPointer, int length)
{
	char *pPrintBuff = NULL;

	if( NULL == pSPointer || 0 >= length ) {
		wfc_util_log_error("wfc_util_printf : unvalid parameters");
		return;
	}

	wfc_util_log_error("wfc_util_printf : lenght is (%d)", length);
	pPrintBuff = malloc(length+1);

	if( NULL != pPrintBuff ) {
		memset( pPrintBuff, 0, (length+1) );
		memcpy(pPrintBuff, pSPointer, length);

		wfc_util_log_error("wfc_util_printf : %s", pPrintBuff);

		free(pPrintBuff);
	} else {
		wfc_util_log_error("wfc_util_printf : can not malloc(%d)", (length+1));
	}
	return;
}
*/

static void wfc_util_finsert_new_string(int fd, char **ppReadedBuff, char *pNewStringValue, char *pEndOfCfg)
{
	off_t sz_file;
	int   sz_backupBuff = 0;
	char *pReadBuff = NULL, *pBackupBuff = NULL;
	char *pSPointer = NULL, *pETagPointer = NULL;

	if( 0 == fd || NULL == pNewStringValue || 0 == strlen(pNewStringValue) ) {
		wfc_util_log_error("wfc_util_finsert_new_string : unvalid parameters");
		return;
	}

	if( NULL == ppReadedBuff) {
		// TODO:
		return;
	} else {
		pReadBuff = *ppReadedBuff;
	}

	/*
	 * find END TAG string
	 */
	pETagPointer = strstr(pReadBuff, pEndOfCfg);
	pSPointer = pETagPointer - 1;

	/*
	 * calcurate file size and size of the tail of file
	 */
	sz_file = lseek( fd,  0, SEEK_END );
	sz_backupBuff = (int)sz_file - (pETagPointer - pReadBuff);

	/*
	 * prefare the buffer to store the tail of file
	 */
	pBackupBuff = malloc(sz_backupBuff);

	if( NULL != pBackupBuff ) {
		/*
		 * copy the tail of file.
		 */
		memset( pBackupBuff, 0, sz_backupBuff );
		memcpy( pBackupBuff, pETagPointer, sz_backupBuff );

		/*
		 * write new string.
		 */
		lseek( fd, (int)(pSPointer-pReadBuff), SEEK_SET );
		write( fd, pNewStringValue, strlen(pNewStringValue));

		/*
		 * update pETagPointer.
		 */
		pETagPointer = pSPointer + strlen(pNewStringValue);

		/*
		 * write the tail of file.
		 */
		lseek( fd, (int)(pETagPointer-pReadBuff), SEEK_SET );
		write( fd, pBackupBuff, sz_backupBuff );

		ftruncate(fd, sz_file + strlen(pNewStringValue) - 1); /* we use "-1" becasue of "pSPointer = pETagPointer - 1"*/

		free(pBackupBuff);

		/*
		 * make new *ppReadedBuff
		 */
		if( NULL != ppReadedBuff) {
			// TODO:
		}
	} else {
		wfc_util_log_error("wfc_util_finsert_new_string : can not malloc(%d)", sz_backupBuff);
	}

	return;
}

static void wfc_util_fupdate_string(int fd, char **ppReadedBuff,
                                    char *pETagPointer, char *pSValuePointer, char *pNewValueString)
{
	off_t sz_file;
	int   sz_newReadBuff = 0;
	char *pReadBuff = NULL, *pNewReadBuff = NULL, *pCurReadBuff = NULL;

	if( 0 == fd ) {
		wfc_util_log_error("wfc_util_fupdate_string : unvalid parameters");
		return;
	}

	if( NULL == ppReadedBuff) {
		// TODO:
		return;
	} else {
		pReadBuff = *ppReadedBuff;
	}

	/*
	 * calcurate file size and new file size
	 */
	sz_file  = lseek( fd,  0, SEEK_END );
	sz_newReadBuff = (int)sz_file - (int)(pETagPointer - pSValuePointer) + strlen(pNewValueString);

	/*
	 * prefare the buffer to read file
	 */
	pNewReadBuff = malloc(sz_newReadBuff);

	if( NULL != pNewReadBuff ) {
		/*
		 * copy buffer
		 */
		memset( pNewReadBuff, 0, sz_file );
		pCurReadBuff = pNewReadBuff;
		memcpy( pNewReadBuff, pReadBuff, (int)(pSValuePointer-pReadBuff) );
		pCurReadBuff += (int)(pSValuePointer-pReadBuff);

		/*
		 * copy new value string
		 */
		memcpy( pCurReadBuff, pNewValueString, strlen(pNewValueString));
		pCurReadBuff += strlen(pNewValueString);

		/*
		 * copy the remained buffer
		 */
		memcpy( pCurReadBuff, pETagPointer, ((int)(sz_file) - (int)(pETagPointer - pReadBuff) + 1));

		/*
		 * write file and update the file size
		 */
		lseek( fd,  0, SEEK_SET );
		write( fd, pNewReadBuff, sz_newReadBuff);
		ftruncate(fd, sz_newReadBuff);

		free(pNewReadBuff);
	} else {
		wfc_util_log_error("wfc_util_fupdate_string : can not malloc(%d)", (int)sz_newReadBuff);
	}

	return;
}

/*
 * wfc_util_fset_buffer
 *
 * return : void
 */
void wfc_util_fset_buffer(char *pFileName, int positionStart, unsigned char *pNewValue, int newValueLength)
{
	int fd;
	off_t sz_file;
	char *pReadBuff = NULL;

	fd = open( pFileName, O_RDWR );

	if( fd >= 0 ) {
		/*
		 * calcurate file size
		 */
		sz_file  = lseek( fd,  0, SEEK_END );

		/*
		 * prefare the buffer to read file
		 */
		pReadBuff = malloc(sz_file + 1);  // null terminated

		if( NULL != pReadBuff ) {
			/*
			 * read file
			 */
			memset( pReadBuff, 0, sz_file + 1);
			lseek( fd,  0, SEEK_SET );
			read( fd, pReadBuff, sz_file );

			if(sz_file >= (positionStart+newValueLength)) {
				lseek( fd, positionStart, SEEK_SET );
				write( fd, pNewValue, newValueLength );
			} else {
				/*
				 * insert with new length value buffer
				 */
				wfc_util_log_error("wfc_util_fset_buffer : file size(%d) is less than to write position(%d)", (int)sz_file, (positionStart+newValueLength));
				// TODO:
			}

			free(pReadBuff);
		} else {
			wfc_util_log_error("wfc_util_fset_buffer : can not malloc(%d)", (int)sz_file);
		}

		if ( -1 == fsync( fd ) ) {
			wfc_util_log_error("wfc_util_fset_buffer : fail to fsync()");
		}

		close( fd );
	} else {
		wfc_util_log_error("wfc_util_fset_buffer : can not open file");
	}

	return;
}

/*
 * wfc_util_fget_buffer
 *
 * return : it will return the length of the stored buffer value if procedure is success
 *          or will return 0 if not.
 */
int wfc_util_fget_buffer(char *pFileName, int positionStart, int lengthToRead, unsigned char *pValueBuff, int buffLength)
{
	int result = 0;
	int fd;
	off_t sz_file;
	char *pReadBuff = NULL;
	char *pSValuePointer = NULL, *pETagPointer = NULL;

	fd = open( pFileName, O_RDONLY );

	if( fd >= 0 ) {
		/*
		 * calcurate file size
		 */
		sz_file  = lseek( fd,  0, SEEK_END );

		if(sz_file >= (positionStart+lengthToRead)) {
			/*
			 * prefare the buffer to read file
			 */
			pReadBuff = malloc(sz_file + 1); // null terminated

			if( NULL != pReadBuff ) {
				/*
				 * read file
				 */
				memset( pReadBuff, 0, sz_file + 1 );
				lseek( fd,  0, SEEK_SET );
				read( fd, pReadBuff, sz_file );

				/*
				 * calculate the start buffer pointer
				 */
				pSValuePointer = pReadBuff + positionStart;

				/*
				 * calculate the end buffer pointer
				 */
				pETagPointer = pSValuePointer + lengthToRead;

				/*
				 * read the string value
				 */
				if( buffLength >= (int)(pETagPointer-pSValuePointer) ) {
					memset( pValueBuff, 0, buffLength );
					memcpy( pValueBuff, pSValuePointer, (int)(pETagPointer-pSValuePointer) );
					result = (int)(pETagPointer-pSValuePointer);
				} else {
					wfc_util_log_error("wfc_util_fget_buffer : not enough string value buffer(%d)", (int)(pETagPointer-pSValuePointer));
				}

				free(pReadBuff);
			} else {
				wfc_util_log_error("wfc_util_fget_buffer : can not malloc(%d)", (int)sz_file);
			}
		} else {
			wfc_util_log_error("wfc_util_fget_buffer : file size(%d) is less than to read position(%d)", (int)sz_file, (positionStart+lengthToRead));
		}
		close( fd );
	} else {
		wfc_util_log_error("wfc_util_fget_buffer : can not open file");
	}

	return result;
}

/*
 * wfc_util_fset_string
 *
 * The following format string will be added or updated to the file pFileName.
 * [pSTagString][pNewValueString][pETagString]
 *
 * pFileName       : file name and path
 * pEndOfCfg       : tag string to notify the end of configuration file
 * pSTagString     : tag string to notify purpose of the value
 * pETagString     : tag string to notify the end of the value
 * pNewValueString : string to set for pSTagString
 *
 * return : void
 */
void wfc_util_fset_string(char *pFileName, char *pEndOfCfg, char *pSTagString, char *pETagString, char *pNewValueString)
{
	int fd;
	off_t sz_file;
	int   sz_NewValueBuff = 0;
	char *pReadBuff = NULL, *pNewValueBuff = NULL;
	char *pSPointer = NULL, *pETagPointer = NULL, *pSValuePointer = NULL;

	fd = open( pFileName, O_RDWR );

	if( fd >= 0 ) {
		/*
		 * calcurate file size
		 */
		sz_file  = lseek( fd,  0, SEEK_END );

		/*
		 * prefare the buffer to read file
		 */
		if (sz_file > 0)
			pReadBuff = malloc(sz_file + 1); // null terminated

		if( NULL != pReadBuff ) {
			/*
			 * read file
			 */
			memset( pReadBuff, 0x00, sz_file + 1);
			if(lseek(fd, 0, SEEK_SET) != 0) {
				wfc_util_log_error("lseek failure");
			}
			read( fd, pReadBuff, sz_file );

			/* WBT fix, make sure it is terminated with \0 */
			pReadBuff[sz_file] = '\0';

			/*
			 * find TAG string
			 */
			pSPointer = strstr(pReadBuff, pSTagString);

			if(NULL != pSPointer) {
				/*
				 * find END OF LINE string
				 */
				pETagPointer = strstr(pSPointer, pETagString);

				if(NULL != pETagPointer) {
					/*
					 * write the new string value
					 */
					pSValuePointer = pSPointer+strlen(pSTagString);
					if(strlen(pNewValueString) == (unsigned int)(pETagPointer-pSValuePointer)) {
						lseek( fd, (int)(pSValuePointer-pReadBuff), SEEK_SET );
						write( fd, pNewValueString, strlen(pNewValueString));
					} else {
						/*
						 * insert with new length value string
						 */
						wfc_util_fupdate_string(fd, &pReadBuff, pETagPointer, pSValuePointer, pNewValueString);
					}
				} else {
					wfc_util_log_error("wfc_util_fset_string : can not find End TAG");
				}
			} else {
				/*
				 * "\n""[Start TAG][String Value][End TAG]""\n"
				 */
				sz_NewValueBuff = strlen(pSTagString) +
				                  strlen(pNewValueString) +
				                  strlen(pETagString) +
				                  2 + 1;
				pNewValueBuff = malloc( sz_NewValueBuff);

				if( NULL != pNewValueBuff ) {
					/*
					 * prefare the new string to insert
					 */
					memset( pNewValueBuff, 0, sz_NewValueBuff );
					sprintf( pNewValueBuff, "%c%s%s%s%c", '\n', pSTagString, pNewValueString, pETagString,'\n' );

					/*
					 * insert new string to the file
					 */
					wfc_util_finsert_new_string(fd, &pReadBuff, pNewValueBuff, pEndOfCfg);

					free( pNewValueBuff );
				} else {
					wfc_util_log_error("wfc_util_fset_string : can not malloc(%d)", (int)sz_file);
				}
			}

			free(pReadBuff);
		} else {
			wfc_util_log_error("wfc_util_fset_string : can not malloc(%d)", (int)sz_file);
		}

		if ( -1 == fsync( fd ) ) {
			wfc_util_log_error("wfc_util_fset_string : fail to fsync()");
		}

		close( fd );
	} else {
		wfc_util_log_error("wfc_util_fset_string : can not open file");
	}

	return;
}

/*
 * wfc_util_fget_string
 *
 * Read value from the following format string in the file pFileName.
 * [pSTagString][string value to read][pETagString]
 *
 * pFileName        : file name and path
 * pEndOfCfg        : tag string to notify the end of configuration file
 * pSTagString      : tag string to notify purpose of the value
 * pETagString      : tag string to notify the end of the value
 * pValueStringBuff : string buffer to get string value
 * stringBuffLength : the length of pValueStringBuff
 *
 * return : it will return the length of the stored string value if procedure is success
 *          or will return 0 if not.
 */
int wfc_util_fget_string(char *pFileName, char *pEndOfCfg __attribute__((unused)), char *pSTagString,
			 char *pETagString, char *pValueStringBuff, int stringBuffLength)
{
	int result = 0;
	int fd;
	off_t sz_file;
	char *pReadBuff = NULL;
	char *pSPointer = NULL, *pETagPointer = NULL, *pSValuePointer = NULL;

	fd = open( pFileName, O_RDONLY );

	if( fd >= 0 ) {
		/*
		 * calcurate file size
		 */
		sz_file  = lseek( fd,  0, SEEK_END );

		/*
		 * prefare the buffer to read file
		 */
		if (sz_file > 0)		// skip when value is 0
			pReadBuff = malloc(sz_file + 1);

		if( NULL != pReadBuff ) {
			/*
			 * read file
			 */
			memset( pReadBuff, 0, sz_file + 1);
			if(lseek(fd, 0, SEEK_SET) != 0) {
				wfc_util_log_error("lseek failure");
			}
			read( fd, pReadBuff, sz_file );

			/* WBT fix, make sure it is terminated with \0 */
			pReadBuff[sz_file] = '\0';

			/*
			 * find TAG string
			 */
			pSPointer = strstr( pReadBuff, pSTagString );

			if( NULL != pSPointer ) {
				/*
				 * find END OF LINE string
				 */
				pETagPointer = strstr(pSPointer, pETagString);

				if( NULL != pETagPointer ) {
					/*
					 * read the string value
					 */
					pSValuePointer = pSPointer+strlen(pSTagString);
					if( stringBuffLength >= (int)(pETagPointer-pSValuePointer) ) {
						memset( pValueStringBuff, 0, stringBuffLength );
						memcpy( pValueStringBuff, pSValuePointer, (int)(pETagPointer-pSValuePointer) );
						result = (int)(pETagPointer-pSValuePointer);
					} else {
						wfc_util_log_error("wfc_util_fget_string : not enough string value buffer(%d)", (int)(pETagPointer-pSValuePointer));
					}
				} else {
					wfc_util_log_error("wfc_util_fget_string : can not find End TAG");
				}
			} else {
				wfc_util_log_error("wfc_util_fget_string : can not find Start TAG");
			}
			free(pReadBuff);
		} else {
			wfc_util_log_error("wfc_util_fget_string : can not malloc(%d)", (int)sz_file);
		}
		close( fd );
	} else {
		wfc_util_log_error("wfc_util_fget_string : can not open file");
	}

	return result;
}

/*
 * wfc_util_ffile_check
 *
 * check whether pDestFName file exist or not
 *
 * pFileName   : file name and path
 * access_mode : R_OK | W_OK | X_OK | F_OK
 *
 * return : it will return 0 if the file exist
 *          or will return -1 if not.
 */
int wfc_util_ffile_check(char *pDestFName, int access_mode)
{
	struct stat st;

	if (access(pDestFName, access_mode) == 0) {
		if( stat( pDestFName, &st ) < 0 ) {
			wfc_util_log_error("Cannot stat the file \"%s\": %s", pDestFName, strerror(errno));
			return -1;
		}
		//check if config file has some data or is it empty due to previous errors
		if( st.st_size ) {
			return 0;
		}
	} else {
		wfc_util_log_error("Cannot access \"%s\": %s", pDestFName, strerror(errno));
	}

	return -1;
}

/*
 * wfc_util_ffile_check_copy
 *
 * check whether pDestFName file exist if not it will copy from pSourceFName file
 *
 * return : it will return 0 if procedure is success
 *          or will return -1 if not.
 */
int wfc_util_ffile_check_copy(char *pDestFName, char *pSourceFName, mode_t mode, uid_t uID, gid_t gID)
{
#define WFC_BUFFER_SIZE 2048
	char buf[WFC_BUFFER_SIZE] = {0}; // Null terminated
	int srcfd, destfd;
	int nread;
	struct stat st;

	if (access(pDestFName, R_OK|W_OK) == 0) {
		if( stat( pDestFName, &st ) < 0 ) {
			wfc_util_log_error("Cannot stat the file \"%s\": %s", pDestFName, strerror(errno));
			return -1;
		}
		//check if config file has some data or is it empty due to previous errors
		if( st.st_size ) {
			return 0;
		}
	//else continue to write the config from default template.
	} else if (errno != ENOENT) {
		wfc_util_log_error("Cannot access \"%s\": %s", pDestFName, strerror(errno));
		return -1;
	}

	srcfd = open(pSourceFName, O_RDONLY);
	if (srcfd < 0) {
		wfc_util_log_error("Cannot open \"%s\": %s", pSourceFName, strerror(errno));
		return -1;
	}

	destfd = open(pDestFName, O_CREAT|O_WRONLY, mode);
	if (destfd < 0) {
		close(srcfd);
		wfc_util_log_error("Cannot create \"%s\": %s", pDestFName, strerror(errno));
		return -1;
	}

	while ((nread = read(srcfd, buf, WFC_BUFFER_SIZE-1)) != 0) {
		if (nread < 0) {
			wfc_util_log_error("Error reading \"%s\": %s", pSourceFName, strerror(errno));
			close(srcfd);
			close(destfd);
			unlink(pDestFName);
			return -1;
		}
		// WBT fix, according to manual, the number of bytes read can't be bigger than read_size. I don't know why WBT complains for this.
		if (nread < WFC_BUFFER_SIZE)
			buf[nread] = '\0';
		else {
			buf[WFC_BUFFER_SIZE-1] = '\0';
			nread = WFC_BUFFER_SIZE-1;
		}
		write(destfd, buf, nread);
	}

	close(destfd);
	close(srcfd);

	/* remove this code because of permission problem when it is accessed from "atd" having system permission. */
	{
	#ifndef CONFIG_LGE_WLAN_WIFI_PATCH
	uid_t uid = getuid();
	gid_t gid = getgid();
	wfc_util_log_error("Error changing group ownership (%d) of %s to %d: %s", gid, pDestFName, gID, strerror(errno));
	if (0 == uid) {
	#endif /* CONFIG_LGE_WLAN_WIFI_PATCH */
		if (chown(pDestFName, uID, gID) < 0) {
			wfc_util_log_error("Error changing group ownership of %s to %d: %s", pDestFName, gID, strerror(errno));
			unlink(pDestFName);
			return -1;
		}
	#ifndef CONFIG_LGE_WLAN_WIFI_PATCH
	} else {
		wfc_util_log_error("wfc_util_ffile_check_copy : we can not excute chown[uid = %d, gid = %d]", uid, getgid());
	}
	#endif /* CONFIG_LGE_WLAN_WIFI_PATCH */
	}

	return 0;
}