//[1] 전처리기 선언부
#include <stdio.h>		// printf, puts, fopen, fwrite, fclose, getchar
#include <malloc.h>		// malloc, free
#include <string.h>		// strcpy, strstr
#include <conio.h>		// getch

//[2] 매크로 상수 선언부
#define ADDRESS_DB		"c:\\AddressDB.txt" //EveryOne에 모든권한 부여

//[3] 구조체 선언부
typedef struct AddressRecord
{
	struct AddressRecord *Prev;		// 이전 레코드를 가리키는 포인터
	char Name[30];					// 이름 필드
	char Mobile[30];				// 전화 필드
	char Address[100];				// 주소 필드
	struct AddressRecord *Next;		// 다음 레코드를 가리키는 포인터
}addressRecord;

//[4] 전역변수 선언부
addressRecord *_Head = NULL;
addressRecord *_Find;
int _IsSave = 1;

//[5] 함수 원형 선언부
void LoadAddress(void);		// 주소록 로드
void RemoveAddress(void);	// 메모리(링크드리스트)에서 해당 레코드 제거

int  AddList(const addressRecord *Address);	// 주소록 메모리 추가
int  FindList(const char *Name);			// 주소록 메모리 찾기

void SetHeadPosition(void);	// _Head 포인터를 처음 포인터로 이동
void SetTailPosition(void);	// _Head 포인터를 마지막 포인터로 이동

void WriteAddress(void);	// 주소록 입력
void ListAddress(void);		// 주소록 출력(리스트)
void ModifyAddress(void);	// 주소록 수정
void DeleteAddress(void);	// 주소록 삭제
void SearchAddress(void);	// 주소록 검색
void SaveAddress(void);		// 파일에 메모리 내용 저장

//[6] 메인 함수
void main(void)
{
	int intChar;

	//이미 파일에 저장된 데이터를 읽어서 메모리 로드
	LoadAddress();

	puts("주소록 프로그램 Version 1.0");

	// 참일 동안 계속반복
	while(1)
	{
		// 메뉴 출력
		printf("\n[1]입력 [2]출력 [3]수정 [4]삭제 [5]검색 ([S]저장 [Q]종료) ");

		intChar = getch();

		switch(intChar)
		{
			case '1' : WriteAddress(); break;
			case '2' : ListAddress(); break;
			case '3' : ModifyAddress(); break;
			case '4' : DeleteAddress(); break;
			case '5' : SearchAddress(); break;
			case 's' :
			case 'S' : SaveAddress(); break;
			case 'q' :
			case 'Q' : 
				if(_IsSave == 0)
				{
					printf("\n\n변경된 주소 데이터를 모두 저장하시겠습니까 (y/n) ? ");
					intChar = getchar();
					if(intChar == 'Y' || intChar == 'y') 
					{
						SaveAddress();
					}
				}
				RemoveAddress(); 
				return;
			default  : printf("\n\n1~5 또는 S/Q를 누르십시오.\n\n"); break; 
		}
	}
}

//[7] 주소록 로드(Load)
void LoadAddress(void)
{
	addressRecord Address;
	FILE *objFile;

	objFile = fopen(ADDRESS_DB, "rb");
	if(objFile == NULL)
	{
		perror("파일 개방 에러");
		return;
	}

	while(!feof(objFile))
	{
		// fread(버퍼, 블럭사이즈, 블럭개수, 파일포인터)
		fread(&Address, sizeof(addressRecord), 1, objFile);

		if(ferror(objFile))
		{
			fclose(objFile);
			perror("파일 읽기 에러");
			return;
		}

		if(feof(objFile))	// 파일의 끝이라면 로드 종료
		{
			break;
		}

		if(AddList(&Address) == 0)
		{
			printf("주소 데이터를 링크드리스트에 추가할 수 없습니다. \n");
		}
	}

	fclose(objFile);
}

//[8] 주소록 추가(Fill)
int AddList(const addressRecord *Address)
{
	addressRecord *arPointer;
	addressRecord *arHeadToTail = _Head;

	SetHeadPosition();

	// _Head가 초기화되지 않은 경우, 한 번만 실행됩니다.
	if(_Head == NULL)
	{
		arPointer = malloc(sizeof(addressRecord));	// addressRecord 크기만큼
		memset(arPointer, 0, sizeof(addressRecord));	// arPointer 구조체에 복사
		if(arPointer == NULL)
		{
			return 0;
		}

		_Head = arPointer;
		_Head->Prev = NULL;			// 처음엔 반드시 NULL로 초기화
		_Head->Next = NULL;			// 처음엔 반드시 NULL로 초기화
	}
	else	// _Head가 초기화 된 후 계속 실행됩니다.
	{
		arPointer = malloc(sizeof(addressRecord));	// addressRecord 구조체 할당
		memset(arPointer, 0, sizeof(addressRecord));
		if(arPointer == NULL)
		{
			return 0;
		}

		while(arHeadToTail->Next)
		{
			arHeadToTail = arHeadToTail->Next;
		}

		arHeadToTail->Next = arPointer;		// 다음 리스트를 지정
		arPointer->Prev = arHeadToTail;		// 이전 리스트를 지정
		arPointer->Next = NULL;	// 다음 리스트를 NULL로 지정
	}

	strcpy(arPointer->Name, Address->Name);
	strcpy(arPointer->Mobile , Address->Mobile );
	strcpy(arPointer->Address, Address->Address);

	return 1;
}

//[9] 주소록 _Head 세팅
void SetHeadPosition(void)
{
	if(_Head == NULL) 
	{	
		return;
	}
	while(_Head->Prev)
	{
		_Head = _Head->Prev;
	}
}

//[10] 주소록 _Tail 세팅
void SetTailPosition(void)
{
	if(_Head == NULL) 
	{
		return;
	}
	while(_Head->Next)
	{
		_Head = _Head->Next;
	}
}

//[11] 로드된 자료중에서 이름 검색
int FindList(const char *Name)
{
	addressRecord *arPointer;
	SetHeadPosition();	// _Head를 첫 주소 데이터를 가리키도록 지정
	arPointer = _Head;

	while(arPointer)
	{
		if(strstr(arPointer->Name, Name))	// 이름을 검색
		{
			_Find = arPointer;
			return 1;
		}
		arPointer = arPointer->Next;
	}

	return 0;
}

//[12] 주소록 저장
void WriteAddress(void)
{
	addressRecord Address;
	memset(&Address, 0, sizeof(addressRecord));

	printf("\n\n등록할 이름 : "); gets(Address.Name);		
	if(strlen(Address.Name) == 0)
	{
		return;
	}
		
	printf("등록할 전화 : "); gets(Address.Mobile);
	printf("등록할 주소 : "); gets(Address.Address);
	if(FindList(Address.Name) == 1)
	{
		printf("\n이미 등록되어 있는 이름입니다. \n\n");
		puts(_Find->Name);
		puts(_Find->Mobile);
		puts(_Find->Address);
		return;
	}

	if(AddList(&Address))
	{
		_IsSave = 0;
		printf("\n등록되었습니다. \n\n");
	}
	else
	{
		printf("\n등록이 실패되었습니다. \n\n");
	}
}

//[13] 주소록 검색
void SearchAddress(void)
{
	char strBuffer[100] = {0, };
	addressRecord *arPointer;

	printf("\n\n검색할 이름/전화/주소의 일부를 입력하세요. \n");
	printf("이름/전화/주소 : "); gets(strBuffer);		
	if(strlen(strBuffer) == 0)
	{
		return;
	}

	SetHeadPosition();

	arPointer = _Head;
	_Find = NULL;

	while(arPointer)
	{
		if(strstr(arPointer->Name, strBuffer))	// 이름을 검색
		{
			_Find = arPointer;
			break;
		}

		if(strstr(arPointer->Mobile , strBuffer))	// 전화를 검색
		{
			_Find = arPointer;
			break;
		}
		
		if(strstr(arPointer->Address, strBuffer))	// 주소를 검색
		{
			_Find = arPointer;
			break;
		}

		arPointer = arPointer->Next;
	}

	if(_Find)
	{
		puts(_Find->Name);
		puts(_Find->Mobile);
		puts(_Find->Address);
	}
	else
	{
		printf("\n\n%s을 주소록에서 찾을 수 없습니다. \n\n", strBuffer);
	}
}

//[14] 주소록 수정
void ModifyAddress(void)
{
	char strName[100] = {0, };
	addressRecord Address;

	while(1)
	{
		printf("\n\n수정할 이름 : "); gets(strName);
		
		if(strlen(strName) == 0)
		{
			return;
		}

		if(FindList(strName) == 0)
		{
			puts("수정할 이름을 찾을 수 없습니다.");
			continue;
		}

		break;
	}

	printf("\n%s에 대한 주소 데이터는 아래와 같습니다. \n\n", strName);
	puts(_Find->Name);
	puts(_Find->Mobile);
	puts(_Find->Address);

	printf("\n수정하려는 이름/전화/주소를 입력한 후 엔터키를 치세요. \n\n");
	
	printf("이름 : "); gets(Address.Name);
	printf("전화 : "); gets(Address.Mobile);
	printf("주소 : "); gets(Address.Address);
	if(strlen(Address.Name) == 0) 
	{
		strcpy(Address.Name, strName);
	}

	strcpy(_Find->Name, Address.Name);
	strcpy(_Find->Mobile , Address.Mobile);
	strcpy(_Find->Address, Address.Address);
	
	_IsSave = 0;

	printf("%s에 대한 주소 데이터를 수정하였습니다. \n", strName);
}

//[15] 주소록 삭제
void DeleteAddress(void)
{
	char strName[100] = { 0, };
	addressRecord *arPointer;
	int intChar;

	while(1)
	{
		printf("\n\n삭제할 이름 : "); gets(strName);		

		if(strlen(strName) == 0) 
		{
			return;
		}

		if(FindList(strName) == 0)
		{
			puts("삭제할 이름을 찾을 수 없습니다.");
			continue;
		}
		break;
	}

	puts(_Find->Name);
	puts(_Find->Mobile);
	puts(_Find->Address);

	printf("%s을 삭제하시겠습니까 (y/n)? ", strName);
	intChar = getch();
	fflush(stdin);	// 키보드 버퍼 비우기
	if(intChar == 'Y' || intChar == 'y')
	{
		if(_Find->Prev == NULL)			// 이전 데이터가 없는 경우
		{
			if(_Find->Next == NULL)		// 다음 데이터도 없는 경우
			{
				free(_Find);
				_Head = NULL;
			}
			else
			{
				arPointer = _Find->Next;
				free(_Find);
				arPointer->Prev = NULL;
				_Head = arPointer;
			}
		}
		else if(_Find->Next == NULL)	// 다음 데이터가 없는 경우
		{
			arPointer = _Find->Prev;
			free(_Find);
			arPointer->Next = NULL;
			_Head = arPointer;
		}
		else					// 이전과 다음데이터가 모두 있는 경우
		{
			arPointer = _Find->Prev;
			arPointer->Next = _Find->Next;
			arPointer = _Find->Next;
			arPointer->Prev = _Find->Prev;
			free(_Find);
			_Head = arPointer;
		}

		_IsSave = 0;

		printf("\n\n검색된 주소 데이터를 삭제하였습니다.\n\n");
	}
}

//[16] 주소록 리스트 보여주기
void ListAddress(void)
{
	int intCount = 1;
	addressRecord *arPointer;

	SetHeadPosition();
	
	arPointer = _Head;

	// arPointer 리스트의 맨 처음으로 이동
	while(arPointer->Prev)
	{
		arPointer = arPointer->Prev;
	}
	printf("\n\n");

	// 한 개씩 출력
	while(arPointer)
	{
		printf("번호. %d \n", intCount++);
		puts(arPointer->Name);
		puts(arPointer->Mobile);
		printf("%s \n\n", arPointer->Address);
		printf("아무키나 누르세요, (중지:q) \n\n");
		if(getch() == 'q')
		{ 
			return;
		}
		arPointer = arPointer->Next;
	}
}

//[17] 주소록 파일 저장
void SaveAddress(void)
{
	addressRecord *arPointer;
	FILE *objFile;
	if(_Head == NULL)
	{
		return;
	}
	
	objFile = fopen(ADDRESS_DB, "w+b");
	if(objFile == NULL)
	{
		perror("파일 개방 에러");
		return;
	}

	SetHeadPosition();

	// 한 개씩 메모리 해제
	while(_Head)
	{
		arPointer = _Head->Next;		
		fwrite(_Head, sizeof(addressRecord), 1, objFile);
		_Head = arPointer;
	}

	printf("\n모든 데이터를 파일에 저장하였습니다.");
	_IsSave = 1;

	fclose(objFile);
}

//[18] 주소록 내용 메모리 해제
void RemoveAddress(void)
{
	addressRecord *arPointer;
	if(_Head == NULL)
	{
		return;
	}
	
	SetHeadPosition();

	// 한 개씩 메모리 해제
	while(_Head)
	{
		arPointer = _Head->Next;		
		free(_Head);		
		_Head = arPointer;
	}

	_Head = NULL; // 재 사용을 하기 위한 초기화
}