/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "charmony.h"

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

#ifndef true
    #define true 1
    #define false 0
#endif

#define CFC_NEED_BASE_STRUCT_DEF
#include "CFCBase.h"
#include "CFCFile.h"
#include "CFCFileSpec.h"
#include "CFCParcel.h"
#include "CFCUtil.h"
#include "CFCClass.h"

struct CFCFile {
    CFCBase base;
    CFCParcel *parcel;
    CFCBase **blocks;
    CFCClass **classes;
    CFCFileSpec *spec;
    int modified;
    char *guard_name;
    char *guard_start;
    char *guard_close;
};

static const CFCMeta CFCFILE_META = {
    "Clownfish::CFC::Model::File",
    sizeof(CFCFile),
    (CFCBase_destroy_t)CFCFile_destroy
};

CFCFile*
CFCFile_new(CFCParcel *parcel, CFCFileSpec *spec) {

    CFCFile *self = (CFCFile*)CFCBase_allocate(&CFCFILE_META);
    return CFCFile_init(self, parcel, spec);
}

CFCFile*
CFCFile_init(CFCFile *self, CFCParcel *parcel, CFCFileSpec *spec) {
    CFCUTIL_NULL_CHECK(parcel);
    CFCUTIL_NULL_CHECK(spec);
    self->parcel     = (CFCParcel*)CFCBase_incref((CFCBase*)parcel);
    self->modified   = false;
    self->spec       = (CFCFileSpec*)CFCBase_incref((CFCBase*)spec);
    self->blocks     = (CFCBase**)CALLOCATE(1, sizeof(CFCBase*));
    self->classes    = (CFCClass**)CALLOCATE(1, sizeof(CFCBase*));

    // Derive include guard name, plus C code for opening and closing the
    // guard.
    const char *path_part = CFCFileSpec_get_path_part(self->spec);
    size_t len = strlen(path_part);
    self->guard_name = (char*)MALLOCATE(len + sizeof("H_") + 1);
    memcpy(self->guard_name, "H_", 2);
    size_t i, j;
    for (i = 0, j = 2; i < len; i++) {
        char c = path_part[i];
        if (c == CHY_DIR_SEP_CHAR) {
            self->guard_name[j++] = '_';
        }
        else if (CFCUtil_isalnum(c)) {
            self->guard_name[j++] = CFCUtil_toupper(c);
        }
    }
    self->guard_name[j] = '\0';
    self->guard_start = CFCUtil_sprintf("#ifndef %s\n#define %s 1\n",
                                        self->guard_name, self->guard_name);
    self->guard_close = CFCUtil_sprintf("#endif /* %s */\n", self->guard_name);

    return self;
}

void
CFCFile_destroy(CFCFile *self) {
    CFCBase_decref((CFCBase*)self->parcel);
    for (size_t i = 0; self->blocks[i] != NULL; i++) {
        CFCBase_decref(self->blocks[i]);
    }
    FREEMEM(self->blocks);
    for (size_t i = 0; self->classes[i] != NULL; i++) {
        CFCBase_decref((CFCBase*)self->classes[i]);
    }
    FREEMEM(self->classes);
    FREEMEM(self->guard_name);
    FREEMEM(self->guard_start);
    FREEMEM(self->guard_close);
    CFCBase_decref((CFCBase*)self->spec);
    CFCBase_destroy((CFCBase*)self);
}

void
CFCFile_add_block(CFCFile *self, CFCBase *block) {
    CFCUTIL_NULL_CHECK(block);
    const char *cfc_class = CFCBase_get_cfc_class(block);

    // Add to classes array if the block is a CFCClass.
    if (strcmp(cfc_class, "Clownfish::CFC::Model::Class") == 0) {
        size_t num_class_blocks = 0;
        while (self->classes[num_class_blocks] != NULL) {
            num_class_blocks++;
        }
        num_class_blocks++;
        size_t size = (num_class_blocks + 1) * sizeof(CFCClass*);
        self->classes = (CFCClass**)REALLOCATE(self->classes, size);
        self->classes[num_class_blocks - 1]
            = (CFCClass*)CFCBase_incref(block);
        self->classes[num_class_blocks] = NULL;
    }

    // Add to blocks array.
    if (strcmp(cfc_class, "Clownfish::CFC::Model::Class") == 0
        || strcmp(cfc_class, "Clownfish::CFC::Model::Parcel") == 0
        || strcmp(cfc_class, "Clownfish::CFC::Model::CBlock") == 0
       ) {
        size_t num_blocks = 0;
        while (self->blocks[num_blocks] != NULL) {
            num_blocks++;
        }
        num_blocks++;
        size_t size = (num_blocks + 1) * sizeof(CFCBase*);
        self->blocks = (CFCBase**)REALLOCATE(self->blocks, size);
        self->blocks[num_blocks - 1] = CFCBase_incref(block);
        self->blocks[num_blocks] = NULL;
    }
    else {
        CFCUtil_die("Wrong kind of object: '%s'", cfc_class);
    }
}

static char*
S_some_path(CFCFile *self, const char *base_dir, const char *ext) {
    const char *path_part = CFCFileSpec_get_path_part(self->spec);
    char *buf;
    if (base_dir) {
        buf = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s%s", base_dir,
                              path_part, ext);
    }
    else {
        buf = CFCUtil_sprintf("%s%s", path_part, ext);
    }
    for (size_t i = 0; buf[i] != '\0'; i++) {
        #ifdef _WIN32
        if (buf[i] == '/') { buf[i] = '\\'; }
        #else
        if (buf[i] == '\\') { buf[i] = '/'; }
        #endif
    }
    return buf;
}

char*
CFCFile_c_path(CFCFile *self, const char *base_dir) {
    return S_some_path(self, base_dir, ".c");
}

char*
CFCFile_h_path(CFCFile *self, const char *base_dir) {
    return S_some_path(self, base_dir, ".h");
}

CFCParcel*
CFCFile_get_parcel(CFCFile *self) {
    return self->parcel;
}

CFCBase**
CFCFile_blocks(CFCFile *self) {
    return self->blocks;
}

CFCClass**
CFCFile_classes(CFCFile *self) {
    return self->classes;
}

void
CFCFile_set_modified(CFCFile *self, int modified) {
    self->modified = !!modified;
}

int
CFCFile_get_modified(CFCFile *self) {
    return self->modified;
}

const char*
CFCFile_get_path(CFCFile *self) {
    return CFCFileSpec_get_path(self->spec);
}

const char*
CFCFile_get_source_dir(CFCFile *self) {
    return CFCFileSpec_get_source_dir(self->spec);
}

const char*
CFCFile_get_path_part(CFCFile *self) {
    return CFCFileSpec_get_path_part(self->spec);
}

int
CFCFile_included(CFCFile *self) {
    return CFCFileSpec_included(self->spec);
}

const char*
CFCFile_guard_name(CFCFile *self) {
    return self->guard_name;
}

const char*
CFCFile_guard_start(CFCFile *self) {
    return self->guard_start;
}

const char*
CFCFile_guard_close(CFCFile *self) {
    return self->guard_close;
}

