/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 21.01.2017
 *
 * Moving files around on the file system.
 *
 */

// TOP

#if !defined(FRED_FILE_MOVING_H)
#define FRED_FILE_MOVING_H

#include <stdio.h>  // include system for windows
#include <stdlib.h> // include system for linux   (YAY!)
#include <stdarg.h>
#include <string.h>

//
// API
//

// System commands
static char SF_CMD[4096];
static i32 error_state = 0;
static i32 prev_error = 0;

#if defined(FM_PRINT_COMMANDS)
#define SYSTEMF_PRINTF(...) printf(__VA_ARGS__);
#else
#define SYSTEMF_PRINTF(...)
#endif

#define systemf(...) do{                                       \
    i32 n = snprintf(SF_CMD, sizeof(SF_CMD), __VA_ARGS__);     \
    Assert(n < sizeof(SF_CMD));                                \
    SYSTEMF_PRINTF("%s\n", SF_CMD);                            \
    prev_error = system(SF_CMD);                               \
    if (prev_error != 0) error_state = 1;                      \
}while(0)

internal void fm_execute_in_dir(char *dir, char *str, char *args);

// Init
internal Arena fm_init_system();

// Timing
internal u64 fm_get_time();

#define LLU_CAST(n) (long long unsigned int)(n)
#define BEGIN_TIME_SECTION() u64 start = fm_get_time()
#define END_TIME_SECTION(n) u64 total = fm_get_time() - start; printf("%-20s: %.2llu.%.6llu\n", (n), LLU_CAST(total/1000000), LLU_CAST(total%1000000));

// Files and Folders Manipulation
internal void fm_make_folder_if_missing(Arena *arena, char *dir);
internal void fm_clear_folder(char *folder);
internal void fm_delete_file(char *file);
internal void fm_copy_file(char *file, char *newname);
internal void fm_copy_all(char *source, char *folder);
internal void fm_copy_folder(Arena *arena, char *src_dir, char *dst_dir, char *src_folder);

// File Reading and Writing
internal void fm_write_file(char *file_name, char *data, u32 size);

// Zip
internal void fm_zip(char *parent, char *folder, char *dest);

// Slash Correction
internal void fm_slash_fix(char *path);

// Memory concat helpers
internal char *fm_prepare_string_internal(Arena *arena, char *s1, ...);
#define fm_str(...) fm_prepare_string_internal(__VA_ARGS__, (void*)0)

internal char *fm_basic_string_internal(Arena *arena, char *s1, ...);
#define fm_basic_str(...) fm_basic_string_internal(__VA_ARGS__, (void*)0)

internal char **fm_prepare_list_internal(Arena *arena, char **l1, ...);
#define fm_list(...) fm_prepare_list_internal(__VA_ARGS__, (void*)0)

internal char **fm_list_one_item(Arena *arena, char *item);

// File System Navigation
internal i32  fm_get_current_directory(char *buffer, i32 max);

struct Temp_Dir{
    char dir[512];
};

internal Temp_Dir fm_pushdir(char *dir);
internal void fm_popdir(Temp_Dir temp);

// Build Line
#define BUILD_LINE_MAX 4096
struct Build_Line{
    char build_optionsA[BUILD_LINE_MAX];
    char build_optionsB[BUILD_LINE_MAX];
    char *build_options;
    char *build_options_prev;
    i32 build_max;
};

internal void fm_init_build_line(Build_Line *line);
internal void fm_finish_build_line(Build_Line *line);

internal void fm__swap_ptr(char **A, char **B);

#if COMPILER_CL

#define fm_add_to_line(line, str, ...) do{  \
    snprintf(line.build_options,            \
    line.build_max, "%s "str,               \
    line.build_options_prev, __VA_ARGS__);  \
    fm__swap_ptr(&line.build_options, &line.build_options_prev); \
}while(0)

#elif COMPILER_GCC | COMPILER_CLANG

#define fm_add_to_line(line, str, ...) do{                   \
    snprintf(line.build_options, line.build_max, "%s " str,  \
    line.build_options_prev, ##__VA_ARGS__);                 \
    fm__swap_ptr(&line.build_options, &line.build_options_prev); \
}while(0)

#endif

// Slashes
#if OS_WINDOWS
# define SLASH "\\"
static char platform_correct_slash = '\\';
#elif OS_LINUX || OS_MAC
# define SLASH "/"
static char platform_correct_slash = '/';
#else
# error Slash not set for this platform.
#endif

// File Extensions
#if OS_WINDOWS
# define EXE ".exe"
#elif OS_LINUX || OS_MAC
# define EXE ""
#else
# error No EXE format specified for this OS
#endif

#if OS_WINDOWS
# define PDB ".pdb"
#elif OS_LINUX || OS_MAC
# define PDB ""
#else
# error No PDB format specified for this OS
#endif

#if OS_WINDOWS
# define DLL ".dll"
#elif OS_LINUX || OS_MAC
# define DLL ".so"
#else
# error No DLL format specified for this OS
#endif

#if OS_WINDOWS
# define BAT ".bat"
#elif OS_LINUX || OS_MAC
# define BAT ".sh"
#else
# error No BAT format specified for this OS
#endif

#endif

//
// Implementation
//

#if defined(FTECH_FILE_MOVING_IMPLEMENTATION) && !defined(FTECH_FILE_MOVING_IMPL_GUARD)
#define FTECH_FILE_MOVING_IMPL_GUARD

internal Arena
fm__init_memory(void){
    return(make_arena_malloc(MB(512), 8));
}

//
// Windows implementation
//

#if OS_WINDOWS

typedef u32 DWORD;
typedef i32  LONG;
typedef i64  LONGLONG;
typedef char*    LPTSTR;
typedef char*    LPCTSTR;
typedef i32  BOOL;
typedef void*    LPSECURITY_ATTRIBUTES;
typedef union    _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG  HighPart;
    };
    struct {
        DWORD LowPart;
        LONG  HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;
typedef void*    HANDLE;
typedef void*    PVOID;
typedef void*    LPVOID;
typedef void*    LPCVOID;
typedef DWORD*   LPDWORD;
#if defined(_WIN64)
typedef unsigned __int64 ULONG_PTR;
#else
typedef unsigned long ULONG_PTR;
#endif
typedef struct _OVERLAPPED {
    ULONG_PTR Internal;
    ULONG_PTR InternalHigh;
    union {
        struct {
            DWORD Offset;
            DWORD OffsetHigh;
        };
        PVOID  Pointer;
    };
    HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;

#define WINAPI

extern "C"{
    DWORD WINAPI GetCurrentDirectoryA(_In_  DWORD  nBufferLength, _Out_ LPTSTR lpBuffer);
    BOOL WINAPI SetCurrentDirectoryA(_In_ LPCTSTR lpPathName);
    BOOL WINAPI QueryPerformanceCounter(_Out_ LARGE_INTEGER *lpPerformanceCount);
    BOOL WINAPI QueryPerformanceFrequency(_Out_ LARGE_INTEGER *lpFrequency);
    BOOL WINAPI CreateDirectoryA(_In_ LPCTSTR lpPathName, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes);
    BOOL WINAPI CopyFileA(_In_ LPCTSTR lpExistingFileName, _In_ LPCTSTR lpNewFileName, _In_ BOOL bFailIfExists);
    
    HANDLE WINAPI CreateFileA(_In_ LPCTSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode,_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile);
    BOOL WINAPI WriteFile(_In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped);
    BOOL WINAPI ReadFile(_In_ HANDLE hFile, _Out_ LPVOID lpBuffer, _In_ DWORD nNumberOfBytesToRead, _Out_opt_ LPDWORD lpNumberOfBytesRead, _Inout_opt_ LPOVERLAPPED lpOverlapped);
    BOOL WINAPI CloseHandle(_In_ HANDLE hObject);
}

#define INVALID_HANDLE_VALUE ((HANDLE) -1)

#define GENERIC_READ                     0x80000000
#define GENERIC_WRITE                    0x40000000
#define GENERIC_EXECUTE                  0x20000000
#define GENERIC_ALL                      0x10000000

#define CREATE_NEW                       1
#define CREATE_ALWAYS                    2
#define OPEN_EXISTING                    3
#define OPEN_ALWAYS                      4
#define TRUNCATE_EXISTING                5

#define FILE_ATTRIBUTE_READONLY          0x00000001
#define FILE_ATTRIBUTE_NORMAL            0x00000080
#define FILE_ATTRIBUTE_TEMPORARY         0x00000100

global u64 perf_frequency;

internal Arena
fm_init_system(void){
    LARGE_INTEGER lint;
    if (QueryPerformanceFrequency(&lint)){
        perf_frequency = lint.QuadPart;
    }
    return(fm__init_memory());
}

internal Temp_Dir
fm_pushdir(char *dir){
    Temp_Dir temp = {};
    GetCurrentDirectoryA(sizeof(temp.dir), temp.dir);
    SetCurrentDirectoryA(dir);
    return(temp);
}

internal void
fm_popdir(Temp_Dir temp){
    SetCurrentDirectoryA(temp.dir);
}

internal u64
fm_get_time(){
    u64 time = 0;
    LARGE_INTEGER lint;
    if (QueryPerformanceCounter(&lint)){
        time = lint.QuadPart;
        time = (time * 1000000) / perf_frequency;
    }
    return(time);
}

internal i32
fm_get_current_directory(char *buffer, i32 max){
    i32 result = GetCurrentDirectoryA(max, buffer);
    return(result);
}

internal void
fm_execute_in_dir(char *dir, char *str, char *args){
    if (dir){
        Temp_Dir temp = fm_pushdir(dir);
        if (args){
            systemf("call \"%s\" %s", str, args);
        }
        else{
            systemf("call \"%s\"", str);
        }
        fm_popdir(temp);
    }
    else{
        if (args){
            systemf("call \"%s\" %s", str, args);
        }
        else{
            systemf("call \"%s\"", str);
        }
    }
}

internal void
fm_slash_fix(char *path){
    if (path != 0){
        for (i32 i = 0; path[i]; ++i){
            if (path[i] == '/') path[i] = '\\';
        }
    }
}

internal void
fm_make_folder_if_missing(Arena *arena, char *dir){
    char *path = fm_str(arena, dir);
    char *p = path;
    for (; *p; ++p){
        if (*p == '\\'){
            *p = 0;
            CreateDirectoryA(path, 0);
            *p = '\\';
        }
    }
    CreateDirectoryA(path, 0);
}

internal void
fm_clear_folder(char *folder){
    fprintf(stdout, "clearing folder %s\n", folder);
    fflush(stdout);
    systemf("del /S /Q /F %s\\* > nul & rmdir /S /Q %s > nul & mkdir %s > nul", folder, folder, folder);
}

internal void
fm_delete_file(char *file){
    systemf("del %s", file);
}

internal void
fm_copy_file(char *file, char *newname){
    printf("copy %s to %s\n", file, newname);
    fflush(stdout);
    CopyFileA(file, newname, 0);
}

internal void
fm_copy_all(char *source, char *folder){
    fprintf(stdout, "copy %s to %s\n", source, folder);
    fflush(stdout);
    systemf("xcopy /s /e /y /q %s %s > nul", source, folder);
}

internal void
fm_write_file(char *file_name, char *data, u32 size){
    HANDLE file = CreateFileA(file_name, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if (file != INVALID_HANDLE_VALUE){
        DWORD written = 0;
        for (;written < size;){
            DWORD newly_written = 0;
            if (!WriteFile(file, data + written, size - written, &newly_written, 0)){
                break;
            }
            written += newly_written;
        }
        Assert(written == size);
        CloseHandle(file);
    }
}

internal void
fm_zip(char *parent, char *folder, char *dest){
    printf("zipping %s\\%s to %s\n", parent, folder, dest);
    fflush(stdout);
    
    char cdir[512];
    fm_get_current_directory(cdir, sizeof(cdir));
    
    Temp_Dir temp = fm_pushdir(parent);
    systemf("%s\\bin\\zip %s\\4ed_gobble.zip > nul", cdir, cdir);
    fm_popdir(temp);
    
    systemf("copy %s\\4ed_gobble.zip %s > nul & del %s\\4ed_gobble.zip > nul", cdir, dest, cdir);
}

//
// Unix implementation
//

#elif OS_LINUX || OS_MAC

#include <time.h>
#include <unistd.h>

internal Temp_Dir
fm_pushdir(char *dir){
    Temp_Dir temp;
    char *result = getcwd(temp.dir, sizeof(temp.dir));
    i32 chresult = chdir(dir);
    if (result == 0 || chresult != 0){
        printf("trying pushdir %s\n", dir);
        Assert(result != 0);
        Assert(chresult == 0);
    }
    return(temp);
}

internal void
fm_popdir(Temp_Dir temp){
    chdir(temp.dir);
}

internal Arena
fm_init_system(){
    return(fm__init_memory());
}

internal u64
fm_get_time(){
    struct timespec spec;
    u64 result;
    clock_gettime(CLOCK_MONOTONIC, &spec);
    result = (spec.tv_sec * (u64)(1000000)) + (spec.tv_nsec / (u64)(1000));
    return(result);
}

internal i32
fm_get_current_directory(char *buffer, i32 max){
    i32 result = 0;
    char *d = getcwd(buffer, max);
    if (d == buffer){
        result = strlen(buffer);
    }
    return(result);
}

internal void
fm_execute_in_dir(char *dir, char *str, char *args){
    if (dir){
        if (args){
            Temp_Dir temp = fm_pushdir(dir);
            systemf("%s %s", str, args);
            fm_popdir(temp);
        }
        else{
            Temp_Dir temp = fm_pushdir(dir);
            systemf("%s", str);
            fm_popdir(temp);
        }
    }
    else{
        if (args){
            systemf("%s %s", str, args);
        }
        else{
            systemf("%s", str);
        }
    }
}

internal void
fm_slash_fix(char *path){}

internal void
fm_make_folder_if_missing(Arena *arena, char *dir){
    systemf("mkdir -p %s", dir);
}

internal void
fm_clear_folder(char *folder){
    fprintf(stdout, "clearing folder %s\n", folder);
    fflush(stdout);
    systemf("rm -rf %s* > /dev/null", folder);
}

internal void
fm_delete_file(char *file){
    systemf("rm %s", file);
}

internal void
fm_copy_file(char *file, char *newname){
    systemf("cp %s %s", file, newname);
}

internal void
fm_copy_all(char *source, char *folder){
    fprintf(stdout, "copy %s to %s\n", source, folder);
    systemf("cp -rf %s %s > /dev/null", source, folder);
}

internal void
fm_write_file(char *file_name, char *data, u32 size){
    // TODO(allen): Real unix version?
    FILE *file = fopen(file_name, "wb");
    if (file != 0){
        fwrite(data, 1, size, file);
        fclose(file);
    }
}

internal void
fm_zip(char *parent, char *folder, char *file){
    Temp_Dir temp = fm_pushdir(parent);
    printf("PARENT DIR: %s\n", parent);
    systemf("zip -r %s %s", file, folder);
    fm_popdir(temp);
}

#else
#error This OS is not supported yet
#endif

internal void
fm_copy_folder(Arena *arena, char *src_dir, char *dst_dir, char *src_folder){
    Temp_Dir temp = fm_pushdir(src_dir);
    fm_make_folder_if_missing(arena, fm_str(arena, dst_dir, "/", src_folder));
    char *copy_name = fm_str(arena, dst_dir, "/", src_folder);
    fm_copy_all(src_folder, copy_name);
    fm_popdir(temp);
}

// List Helpers
internal i32
listsize(void *p, u64 item_size){
    u64 zero = 0;
    u8 *ptr = (u8*)p;
    for (;memcmp(ptr, &zero, (size_t)item_size) != 0; ptr += item_size);
    i32 size = (i32)(ptr - (u8*)p);
    return(size);
}

internal void*
fm__prepare(Arena *arena, i32 item_size, void *i1, va_list list){
    List_String_Const_char out_list = {};
    i32 size = listsize(i1, item_size);
    string_list_push(arena, &out_list, SCchar((char*)i1, size));
    void *ln = va_arg(list, void*);
    for (;ln != 0;){
        size = listsize(ln, item_size);
        string_list_push(arena, &out_list, SCchar((char*)ln, size));
        ln = va_arg(list, void*);
    }
    void *terminator = push_array_zero(arena, char, item_size);
    string_list_push(arena, &out_list, SCchar((char*)terminator, item_size));
    String_Const_char result = string_list_flatten(arena, out_list);
    return(result.str);
}

internal char*
fm_basic_string_internal(Arena *arena, char *s1, ...){
    i32 item_size = sizeof(*s1);
    va_list list;
    va_start(list, s1);
    char *result = (char*)fm__prepare(arena, item_size, s1, list);
    va_end(list);
    return(result);
}

internal char*
fm_prepare_string_internal(Arena *arena, char *s1, ...){
    i32 item_size = sizeof(*s1);
    va_list list;
    va_start(list, s1);
    char *result = (char*)fm__prepare(arena, item_size, s1, list);
    va_end(list);
    fm_slash_fix(result);
    return(result);
}

internal char**
fm_prepare_list_internal(Arena *arena, char **p1, ...){
    i32 item_size = sizeof(*p1);
    va_list list;
    va_start(list, p1);
    char **result = (char**)fm__prepare(arena, item_size, p1, list);
    va_end(list);
    return(result);
}

internal char**
fm_list_one_item(Arena *arena, char *item){
    char **result = push_array(arena, char*, 2);
    result[0] = item;
    result[1] = 0;
    return(result);
}

// Build Line
internal void
fm_init_build_line(Build_Line *line){
    line->build_options = line->build_optionsA;
    line->build_options_prev = line->build_optionsB;
    line->build_optionsA[0] = 0;
    line->build_optionsB[0] = 0;
    line->build_max = BUILD_LINE_MAX;
}

internal void
fm_finish_build_line(Build_Line *line){
    fm__swap_ptr(&line->build_options, &line->build_options_prev);
}

internal void
fm__swap_ptr(char **A, char **B){
    char *a = *A;
    char *b = *B;
    *A = b;
    *B = a;
}

#endif

// BOTTOM