/*
 This is Program of edline for c
 Copyright (c) 2015 yoshihiro watanabe
*/
#define _CRT_SECURE_NO_WARNINGS

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_CHAR 65536
#define NOT_SET -1
#define SUCCESS 0
#define ERROR_ARGUMENT_NUMBER 1
#define ERROR_CANNOT_OPEN_FILE 2
#define ERROR_CANNOT_CLOSE_FILE 3

char *ErrorMessage[4] = {
	"成功",
	"ファイル名を指定してください(-?でヘルプ)",
	"ファイルを開けませんでした。",
	"ファイルを閉じれませんでした。"
};

char *HelpMessage =
"a:追加(行頭のCtrl+Zで終了)\n"
"d:削除([1d][1-2d]のように行指定)\n"
"i:挿入([1i]のように行指定、行頭のCtrl+Zで終了)\n"
"l:表示([2l][2-5l]のように行指定)\n"
"q:終了\n(行番号):編集\n";

char *FileName;
char **Lines = NULL;
int LineCount = 0;

void Append();
void Delete(int start, int end);
void Edit(int start);
char GetCommandChar(char *command);
int GetStart(char *command);
int GetEnd(char *command);
void Insert(int start);
int Interact();
void List(int start, int end);
int LoadFile(char *fileName);
int Quit();
int SaveFile(char *fileName);
void PrintLine(int lineNum, char *line);

int main(int argc, char *argv[]) {
	int i;
	int result = SUCCESS;

	if (argc != 2) {
		result = ERROR_ARGUMENT_NUMBER;
	}
	else {
		if (argv[1][0] == '-') {
			switch (argv[1][1]) {
			case 'v':
				printf("edline Version 0.01 2015(c) yoshihiro watanabe\n");
				return result;
			case 'h':
				printf("ラインエディタedline\n\n"
					"起動方法:%s ファイル名\n\n"
					"コマンド:\n%s", argv[0], HelpMessage);
				return result;
			}
		}
		FileName = argv[1];
		result = LoadFile(FileName);
		printf("%s:", FileName);
		if (result == ERROR_CANNOT_OPEN_FILE) {
			printf("新しいファイルです。\n");
		}
		else {
			printf("%d行読み込みました。\n", LineCount);
		}
		result = Interact();
	}

	for (i = 0; i < LineCount; i++) {
		if (Lines[i]) {
			free(Lines[i]);
		}
	}
	if (Lines) {
		free(Lines);
	}

	if (result != SUCCESS) {
		printf("%s\n", ErrorMessage[result]);
	}
	return result;
}

void PrintLine(int lineNum, char *line) {
	printf("%5d:%s", lineNum, line);
}

void Append() {
	int i;
	char line[MAX_CHAR];
	char **oldLines = NULL;

	PrintLine( LineCount + 1, "");
	while ((int)NULL != gets(line)) {
		strcat(line, "\n");
		LineCount++;
		if (!Lines) {
			Lines = (char **)calloc(LineCount, sizeof(char*));
		}
		else {
			oldLines = Lines;
			Lines = (char **)calloc(LineCount, sizeof(char *));
			for (i = 0; i < LineCount - 1; i++) {
				Lines[i] = oldLines[i];
			}
			free(oldLines);
		}
		Lines[LineCount - 1] = malloc(sizeof(char)*(strlen(line)+1));
		strcpy(Lines[LineCount - 1], line);
		PrintLine(LineCount + 1, "");
	}
}
void Delete(int start, int end) {
	int i;
	int howManyLines = end - start + 1;
	char **oldLines = NULL;

	if (howManyLines > 0) {
		if (LineCount > 0) {
			LineCount -= howManyLines;
			oldLines = Lines;
			if (LineCount == 0) {
				Lines = NULL;
			}
			else {
				Lines = (char**)calloc(LineCount, sizeof(char *));
			}
			for (i = 0; i < start - 1; i++) {
				Lines[i] = oldLines[i];
			}
			for (i = start - 1; i < end; i++) {
				free(oldLines[i]);
			}
			for (i = start - 1; i < LineCount; i++) {
				Lines[i] = oldLines[i + howManyLines];
			}
			free(oldLines);
		}
	}
	else {
		printf("削除できる行がありません。\n");
	}
}
void Edit(int start) {
	char line[MAX_CHAR];

	if (LineCount > 0) {
		PrintLine(start, Lines[start - 1]);
		PrintLine(start, "");
		if ((int)NULL != gets(line)) {
			strcat(line, "\n");
			free(Lines[start - 1]);
			Lines[start - 1] = (char*)malloc(sizeof(char)*(strlen(line) + 1));
			strcpy(Lines[start - 1], line);
		}
	}
	else {
		printf("編集できる行がありません。\n");
	}
}
char GetCommandChar(char *command) {
	char commandChar[] = "adhilqADHILQ?";
	unsigned int i;

	for (i = 0; i < strlen(commandChar); i++) {
		if (NULL != strchr(command, commandChar[i])){
			return tolower(commandChar[i]);
		}
	}
	return 0;
}
int GetStart(char *command) {
	char digit[11];
	int j = 0,num;
	unsigned int i;

	if (!isdigit(command[0])) {
		return NOT_SET;
	}
	for (i = 0; i < strlen(command); i++) {
		if (isdigit(command[i])) {
			digit[j] = command[i];
			j++;
		}
		else {
			break;
		}
	}
	digit[j] = '\0';
	num = atoi(digit);
	if (num < 1) return 1;
	if (num > LineCount) return LineCount;
	return num;
}
int GetEnd(char *command) {
	char digit[11];
	int j = 0, num;
	unsigned int i;
	char *start;
	if ((start = strchr(command, '-')) == NULL) {
		return NOT_SET;
	}
	for (i = 1; i < strlen(start); i++) {
		if (isdigit(start[i])) {
			digit[j] = start[i];
			j++;
		}
		else {
			break;
		}
	}
	if (j == 0) {
		return NOT_SET;
	}
	else {
		digit[j] = '\0';
		num = atoi(digit);
		if (num < 1) return 1;
		if (num > LineCount) return LineCount;
		return num;
	}
}
void Insert(int start) {
	int i;
	char line[MAX_CHAR];
	char **oldLines = NULL;

	if (LineCount > 0) {
		PrintLine(start, "");
		while ((int)NULL != gets(line)) {
			strcat(line, "\n");
			LineCount++;
			oldLines = Lines;
			Lines = (char**)calloc(LineCount, sizeof(char*));
			for (i = 0; i < start - 1;i++)
			{
				Lines[i] = oldLines[i];
			}
			Lines[start - 1] = (char*) malloc(sizeof(char) * (strlen(line) + 1) );
			strcpy(Lines[start - 1], line);
			for (i = start; i < LineCount; i++) {
				Lines[i] = oldLines[i - 1];
			}
			free(oldLines);
			start++;
			PrintLine(start,"");
		}
	}
	else {
		Append();
	}

}
int Interact() {
	char command[MAX_CHAR];
	char commandChar;
	int start, end;

	while (1) {
		printf("%c", '*');
		if ((int)NULL == gets(command)) {
			continue;
		}
		if (command != "") {
			commandChar = GetCommandChar(command);
			start = GetStart(command);
			end = GetEnd(command);

			switch (commandChar)
			{
			case 'q':
				return Quit();
			case 'l':
				if (start == NOT_SET) start = 1;
				if (end == NOT_SET) end = LineCount;
				List(start, end);
				break;
			case 'a':
				Append();
				break;
			case 'i':
				if (start == NOT_SET) {
					Append();
				}
				else
				{
					Insert(start);
				}
				break;
			case 'd':
				if (start == NOT_SET) {
					printf("行を指定してください\n");
				}
				else {
					if (end == NOT_SET) end = start;
					Delete(start, end);
				}
				break;
			case 'h': case '?':
				printf("%s", HelpMessage);
				break;
			default:
				if (start != NOT_SET) {
					Edit(start);
				}
			}
		}
	}
	return 0;
}
void List(int start, int end) {
	int i;
	if (LineCount > 0) {
		for (i = start; i <= end;i++) {
			PrintLine(i, Lines[i - 1]);
		}
	}
	else {
		printf("表示できる行がありません\n");
	}
}
int LoadFile(char *fileName) {
	char buffer[MAX_CHAR];
	char c;
	FILE *fp;
	int i = 0;
	char **pLine = NULL;
	int result = SUCCESS;

	fp = fopen(fileName, "r");
	if (fp == NULL) {
		result = ERROR_CANNOT_OPEN_FILE;
	}
	else {
		while (NULL != fgets(buffer, MAX_CHAR, fp)) {
			LineCount++;
		}
		rewind(fp);

		Lines = (char**)calloc(LineCount, sizeof(char*));
		pLine = Lines;

		c = '\0';
		while (c != EOF) {
			c = getc(fp);
			if ((c == '\n') || ((c == EOF) && (i != 0))) {
				buffer[i] = '\n';
				buffer[i + 1] = '\0';
				*pLine = (char*)malloc(sizeof(char)*(i + 2));
				strcpy(*pLine, buffer);
				pLine++;
				i = 0;
			}
			else {
				buffer[i] = c;
				i++;
			}
		}
		if (0 != fclose(fp)) {
			result = ERROR_CANNOT_CLOSE_FILE;
		}
	}
	return result;
}
int Quit() {
	char command;
	int result = SUCCESS;

	printf("保存しますか?(Y/N):");
	command = tolower(getchar());
	if (command == 'y'){
		result = SaveFile(FileName);
	}
	return result;
}
int SaveFile(char *fileName) {
	FILE *fp;
	int i;
	int result = SUCCESS;

	if (LineCount > 0) {
		fp = fopen(fileName, "w");
		if (fp == NULL) {
			result = ERROR_CANNOT_OPEN_FILE;
		}
		else {
			for (i = 0; i < LineCount; i++) {
				fputs(Lines[i], fp);
			}
			if (0 != fclose(fp)) {
				result = ERROR_CANNOT_CLOSE_FILE;
			}
		}
	}
	else {
		remove(fileName);
	}
	return result;
}
