/*  Här är några C-funktioner, främst för in- och utmatning,
 *  som kanske kan vara användbara
 *  - särskilt för den som inte är så van vid text-I/O i C.
 *
 *  Gratis programvara utan garanti. Använd den hur ni vill, men skyll inte på mig.
 *
 *  Kompilerat och provkört, utan ändringar, på:
 *   Red Hat Linux 8.0, gcc 3.2, unixODBC 2.2.2
 *     Som C: gcc -Wall -o testa-c-funktionerna testa-c-funktionerna.c c-funktioner.c
 *     Som C++: g++ -Wall -o testa-c-funktionerna testa-c-funktionerna.cc c-funktioner.cc
 *   Windows 2000 Professional, Microsoft Visual C++ 6.0
 *   Windows 2000 Professional, Borland C++ Builder 5.0
 *
 *  Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se)
 *  Denna fil senast ändrad: 18 maj 2004
 */

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

#include "c-funktioner.h"

/*---------------------------------------------------------------------------*/

/*  Funktionen "fatal_error" skriver ut ett felmeddelande och avslutar
 *  programmet. Den anropas med en formatsträng och ytterligare argument
 *  på samma sätt som printf.
 */

void fatal_error(char* format_string, ...) {
    va_list argp;
#if defined(_MSDOS) || defined(_WIN32)
    int c;
#endif

    fflush(stdout);
    fflush(stderr);

    fprintf(stderr, "\007\n"
                    "*** Det har uppstått ett fel:\n"
		    "    ");
    fflush(stderr);

    va_start(argp, format_string);
    vfprintf(stderr, format_string, argp);
    va_end(argp);
    fprintf(stderr, "\n");
    fprintf(stderr, "    Möjlig felorsak: %s (felkod %d)\n", strerror(errno), errno);
    fprintf(stderr, "    Programmet avslutas.\n");

    fflush(stdout);
    fflush(stderr);

    /* Some systems, such as Borland C++ on Windows,
       just closes the text window when the program exits. */
#if defined(_MSDOS) || defined(_WIN32)
    printf("Press RETURN to exit the program: ");
    while ((c = getchar()) != '\n' && c != EOF)
        ;
#endif

    exit(EXIT_FAILURE);
} /* fatal_error */

/*---------------------------------------------------------------------------*/

/*  Det här är "säkra" versioner av minnesallokeringsfunktionerna
 *  "malloc", "free" m fl. De anropar "fatal_arror" om det inte gick att
 *  allokera det önskade minnet.
 *  Dessutom görs en del kontroller så att funktionerna ska följa
 *  C-standarden även om biblioteksfunktionerna kanske inte gör det.
 */

void *safe_malloc(size_t s) {
    void *result;
    size_t i;

    result = malloc(s == 0 ? 1 : s);
    if (result == NULL)
        fatal_error("Minnet fullt. Kunde inte allokera %lu bytes.",
		    (unsigned long)s);
    for (i = 0; i < s; ++i)
      ((char*)result)[i] = 'N';
    return result;
} /* safe_malloc */

void *safe_calloc(size_t n, size_t s) {
    void *result;
    
    result = calloc(n == 0 ? 1 : 0, s == 0 ? 1 : 0);
    if (result == NULL)
        fatal_error("Minnet fullt. Kunde inte allokera %lu bytes.",
		    (unsigned long)n * (unsigned long)s);
    return result;
} /* safe_calloc */

void *safe_realloc(void *p, size_t s) {
    void *result;
    
    if (p == NULL)
        result = malloc(s ? s : 1);
    else
        result = realloc(p, s ? s : 1);
    if (result == NULL)
        fatal_error("Minnet fullt. Kunde inte allokera %lu bytes.",
		    (unsigned long)s);
    return result;
} /* safe_realloc */

void safe_free(void *p) {
    if (p != NULL)
        free(p);
} /* safe_free */

/*---------------------------------------------------------------------------*/

/*  Den här funktionen returnerar 1 om användaren svarar 'j', 0 om 'n'.
 *  Om man skriver något annat frågar den om.
 */

int ask_yes_or_no(char *question) {
    int c, junk;

    do {
	printf("%s (skriv 'j' eller 'n') ", question);
	fflush(stdout);
	c = getchar();
	if (c == EOF) {
	    printf("Varning: Filslut när antingen 'j' eller 'n' skulle läsas.\n");
	    fflush(stdout);
	    return 0;
	}

	if (c != '\n')
	    while ((junk = getchar()) != '\n' && junk != EOF)
		;
	if (c == 'j')
	    return 1;
	else if (c == 'n')
	    return 0;
	else {
	    printf("\007");
	    printf("Nej! Fel! Skriv 'j' eller 'n' först på raden!\n");
	    fflush(stdout);
	}
    } while (1);
} /* ask_yes_or_no */

/*---------------------------------------------------------------------------*/

/*  "smart_fgets" är en mer "praktisk" variant av funktionen "fgets".
 *  Den gör en del kontroller, och det avslutande radslutstecknet
 *  kommer inte med i bufferten, utan kastas bara bort.
 *  "buffer" ska peka på den plats där den inlästa raden ska lagras,
 *  "buffer_size" är storleken på "buffer" (inklusive den avslutande nul-byten),
 *  och "instream" är den öppna fil man ska läsa från.
 */

char *smart_fgets(char *buffer, int buffer_size, FILE *instream) {
    char *fgets_result;
    int length;

    fgets_result = fgets(buffer, buffer_size, instream);
    if (fgets_result == NULL)
	return NULL;
    length = strlen(buffer);
    if (buffer[length - 1] == '\n')
	buffer[length - 1] = '\0';
    else if (!feof(instream)) {
	printf("Varning: För många tecken på raden (max antal tecken = %d).\n",
	       buffer_size - 1);
	printf("         Överskjutande tecken kastas bort.\n");
	while (getc(instream) != '\n')
	    ;
    }
    printf("Inmatad sträng: '%s'.\n", buffer);
    return fgets_result;
} /* smart_fgets */

/*---------------------------------------------------------------------------*/

/*  Funktionen "f_read_line" läser en hel rad från filen "infile"
 *  och returnerar den i en malloc-allokerad sträng.
 *  Det avslutande radslutstecknet kommer inte med i bufferten,
 *  utan kastas bara bort.
 *  Vid filslut returneras NULL.
 */

#define BUFFER_INCREMENT        100

char *f_read_line(FILE *infile) {
    char *buffer;
    int buffer_size, string_length;
    int this_char;

    this_char = getc(infile);
    if (this_char == EOF) {
	printf("Varning: Filslut när en rad skulle läsas.\n");
        return NULL;
    }

    buffer = (char*)safe_malloc(BUFFER_INCREMENT);
    buffer_size = BUFFER_INCREMENT;
    string_length = 0;

    while (this_char != EOF && this_char != '\n') {
        /* Lägg in det inlästa tecknet i bufferten, som växer om det behövs */
        buffer[string_length++] = this_char;
        if (string_length >= buffer_size) {
            buffer_size += BUFFER_INCREMENT;
            buffer = (char*)safe_realloc(buffer, buffer_size);
        }
        this_char = getc(infile);
    }

    /* Nu krymper (eller utökar!) vi bufferten så den blir precis lagom stor */
    if (string_length + 1 != buffer_size)
        buffer = (char*)safe_realloc(buffer, string_length + 1);
    buffer[string_length] = '\0';

    return buffer;
} /* f_read_line */

/*---------------------------------------------------------------------------*/

/*  Funktionen "read_line" läser en hel rad från standardinmatningen
 *  och returnerar den i en malloc-allokerad sträng.
 *  Det avslutande radslutstecknet kommer inte med i bufferten,
 *  utan kastas bara bort.
 *  Vid filslut returneras NULL.
 */

char *read_line(void) {
    return f_read_line(stdin);
} /* read_line */

/*---------------------------------------------------------------------------*/

/*  Funktionen "f_read_integer" läser ett heltal från filen "infile",
 *  och returnerar det heltalet.
 */

int f_read_integer(FILE *infile) {
    int ok_integer;
    char *the_line = NULL;
    int scanf_result;
    int the_integer;

    do {
	scanf_result = fscanf(infile, "%d", &the_integer);
	if (scanf_result == EOF) {
	    printf("Varning: Filslut när ett heltal skulle läsas.\n");
	    ok_integer = 1;
	    the_integer = 0;
	}
	else if (scanf_result != 1) {
	    the_line = f_read_line(infile);
	    if (the_line == NULL) {
		ok_integer = 1;
		the_integer = 0;
	    }
	    else {
		printf("Nej! Fel! '%s' är inte ett tillåtet heltal. Försök igen!\n", the_line);
		ok_integer = 0;
	    }
	}
	else
	    ok_integer = 1;
    } while (! ok_integer);

    if (the_line != NULL)
	safe_free(the_line);
    return the_integer;
} /* f_read_integer */

/*---------------------------------------------------------------------------*/

/*  Funktionen "read_integer" läser ett heltal från standardinmatningen,
 *  och returnerar det heltalet.
 */

int read_integer(void) {
    return f_read_integer(stdin);
} /* read_integer */

/*---------------------------------------------------------------------------*/

/*  Funktionen "f_read_integer_line" läser en rad från filen "infile",
 *  kollar att den raden verkligen innehåller ett heltal,
 *  och returnerar det heltalet.
 */

int f_read_integer_line(FILE *infile) {
    int ok_integer;
    char *the_line = NULL;
    char *current_cp;
    int position_after_integer;
    int scanf_result;
    int the_integer;

    do {
	if ((the_line = f_read_line(infile)) == NULL) {
	    printf("Varning: Filslut när en rad med ett heltal skulle läsas.\n");
	    ok_integer = 1;
	    the_integer = 0;
	}
	else if ((scanf_result = sscanf(the_line, "%d%n", &the_integer, &position_after_integer)) != 1) {
	    printf("Nej! Fel! '%s' är inte ett tillåtet heltal. Försök igen!\n", the_line);
	    ok_integer = 0;
	}
	else {
	    current_cp = the_line + position_after_integer;
	    while (isspace((unsigned char)*current_cp))
		++current_cp;
	    if (*current_cp != '\0') {
		printf("Nej! Fel! Skräp efter heltalet %d på raden '%s'. Försök igen!\n", the_integer, the_line);
		ok_integer = 0;
	    }
	    else
		ok_integer = 1;
	}
    } while (! ok_integer);

    if (the_line != NULL)
	safe_free(the_line);
    return the_integer;
} /* f_read_integer_line */

/*---------------------------------------------------------------------------*/

/*  Funktionen "read_integer_line" läser en rad från standardinmatningen,
 *  kollar att den raden verkligen innehåller ett heltal,
 *  och returnerar det heltalet.
 */

int read_integer_line(void) {
    return f_read_integer_line(stdin);
} /* read_integer_line */

/*---------------------------------------------------------------------------*/

/* Den här funktionen "kapar bort" blanktecken på slutet i en sträng: */

void skip_trailing_space(char *str) {
    char *cp;

    cp = str;
    while (*cp)
        ++cp;
    --cp;
    while (cp >= str && isspace((unsigned char)*cp))
        --cp;
    cp[1] = '\0';
} /* skip_trailing_space */

/*---------------------------------------------------------------------------*/

/*  Den här funktionen "slår ihop" två strängar genom att malloc-allokera
 *  plats för resultatet, kopiera dit båda strängarna, och returnera
 *  en pekare till den malloc-allokerade strängen.
 */

char *add_strings(char *str1, char *str2) {
    int length1, length2;
    char *result;

    length1 = strlen(str1);
    length2 = strlen(str2);

    result = (char*)malloc(length1 + length2 + 1);
    if (result != NULL) {
	strcpy(result, str1);
	strcpy(&result[length1], str2);	/* Alternativt: strcat(result, str2); */
    }

    return result;
} /* add_strings */

/*---------------------------------------------------------------------------*/

