#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "lz77.h"

const uint32_t BUFFER_SIZE = 1024;

int write_u8(FILE *file, const uint8_t v)
{
    return fwrite(&v, sizeof(uint8_t), 1, file);
}

int write_u16(FILE *file, const uint16_t v)
{
    return fwrite(&v, sizeof(uint16_t), 1, file);
}

int write_u32(FILE *file, const uint32_t v)
{
    return fwrite(&v, sizeof(uint32_t), 1, file);
}

int copy_file_to_file(FILE *input, FILE *output, uint32_t bytes)
{
    uint8_t buffer[BUFFER_SIZE];
    uint32_t bytes_written = 0;

    for (uint32_t start = 0; start < bytes; start += BUFFER_SIZE)
    {
        uint32_t remaining_bytes = bytes - start;
        uint32_t copy_this_iter = (remaining_bytes < BUFFER_SIZE) ?
                        remaining_bytes : BUFFER_SIZE;

        fread(buffer, 1, copy_this_iter, input);
        bytes_written += fwrite(buffer, 1, copy_this_iter, output);
    }

    return bytes_written;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        fprintf(stderr, "Usage: %s <output zip filename> <input file filename>\n", argv[0]);
        return 1;
    }

    const char *output_file_name = argv[1];
    const char *input_file_name = argv[2];
    FILE *input_file = fopen(input_file_name, "r");
    FILE *output_file = fopen(output_file_name, "w+");

    if (input_file == NULL)
    {
        fprintf(stderr, "Could not open \"%s\"\n", input_file_name);
        return 1;
    }

    if (output_file == NULL)
    {
        fprintf(stderr, "Could not open \"%s\"\n", output_file_name);
        return 2;
    }



    fseek(input_file, 0, SEEK_END);
    uint32_t input_file_size = ftell(input_file);
    fseek(input_file, 0, SEEK_SET);

    /* (1) LOCAL FILE RECORD */
    write_u32(output_file, 0x04034b50); /* local file signature */
    write_u16(output_file, 20); /* extract version */
    write_u16(output_file, 0); /* general purpose flag */
    write_u16(output_file, 8); /* compression method */
    write_u16(output_file, 0); /* last mod file time */
    write_u16(output_file, 0); /* last mod file date */
    write_u32(output_file, 0xDEADBEEF); /* crc */
    size_t compressed_size_loc = ftell(output_file); 
    write_u32(output_file, 0); /* compressed file size */
    write_u32(output_file, input_file_size); /* uncompressed file size */
    write_u16(output_file, strlen(input_file_name)); /* file name len */
    write_u16(output_file, 0); /* extra field length */
    fputs(input_file_name, output_file); /* file name */
    /* extra field */
    /* copy input file to output */
    uint32_t compressed_size = write_lz77_stream(input_file, output_file);
    /* printf("Compressed size = %d\n", compressed_size); */

    size_t central_directory_start = ftell(output_file); /* save location */
    fseek(output_file, compressed_size_loc, SEEK_SET); /* move back */
    write_u32(output_file, compressed_size); /* write compressed size */
    fseek(output_file, central_directory_start, SEEK_SET); /* return to writing */

    /* (2) CENTRAL DIRECTORY RECORD */
    write_u32(output_file, 0x02014b50); /* central directory signature */
    write_u8 (output_file, 30); /* specification version */
    write_u8 (output_file, 65); /* made by */
    write_u16(output_file, 20); /* extract version */
    write_u16(output_file, 0); /* general purpose bit flag */
    write_u16(output_file, 8); /* compression method */
    write_u16(output_file, 0); /* last mod file time */
    write_u16(output_file, 0); /* last mod file date */
    write_u32(output_file, 0xdeadbeef); /* crc */
    write_u32(output_file, compressed_size); /* compressed file size */
    write_u32(output_file, input_file_size); /* uncompressed file size */
    write_u16(output_file, strlen(input_file_name)); /* file name length */
    write_u16(output_file, 0); /* extra field length */
    write_u16(output_file, 0); /* file comment length */
    write_u16(output_file, 0); /* disk number start */
    write_u16(output_file, 1); /* internal file attributes */
    write_u32(output_file, 1); /* external file attributes */
    write_u32(output_file, 0); /* offset of local header */
    fputs(input_file_name, output_file); /* file name */
    /* extra field */
    /* file comment */

    uint32_t end_record_start = ftell(output_file);

    /* (3) END OF CENTRAL DIRECTORY RECORD */
    write_u32(output_file, 0x06054b50); /* end of central dir signature */
    write_u16(output_file, 0); /* number of this disk */
    write_u16(output_file, 0); /* number of the start disk */
    write_u16(output_file, 1); /* total number of entries on this disk */
    write_u16(output_file, 1); /* total number of entries */
    write_u32(output_file, end_record_start - central_directory_start); /* size of central directory record */
    write_u32(output_file, central_directory_start); /* offset of start of central directory */
    write_u16(output_file, 0); /* .zip file comment length */
    /* .zip file comment */

    fclose(input_file);
    fclose(output_file);
    return 0;
}