# include <string.h>
# include "Pcore/Lib/Path.h"

struct Tokens {
    size_t len;
    int is_dots;
    U8 *token;
};

void destroyPcoreLibPath (PcoreLibPath *path) {
    Safefree(path->path);
    Safefree(path->volume);
    Safefree(path->dirname);
    Safefree(path->filename);
    Safefree(path->filename_base);
    Safefree(path->suffix);
    Safefree(path);
}

PcoreLibPath *parse (const char *buf, size_t buf_len) {
    PcoreLibPath *res;
    Newx(res, 1, PcoreLibPath);

    res->is_abs = 0;
    res->path_len = 0;
    res->path = NULL;
    res->volume_len = 0;
    res->volume = NULL;
    res->dirname_len = 0;
    res->dirname = NULL;
    res->filename_len = 0;
    res->filename = NULL;
    res->filename_base_len = 0;
    res->filename_base = NULL;
    res->suffix_len = 0;
    res->suffix = NULL;

    // buf is not empty
    if (buf_len) {
        U8 prefix[3];
        size_t prefix_len = 0;
        size_t i = 0;

        // parse leading "/"
        if (buf[0] == '/' || buf[0] == '\\') {
            prefix[0] = '/';
            prefix_len = 1;
            i = 1;

            res->is_abs = 1;
        }

# ifdef WIN32

        // parse windows volume
        else if ( buf_len >= 2 && buf[1] == ':' && ( buf[2] == '/' || buf[2] == '\\' ) && isalpha(buf[0]) ) {
            prefix[0] = tolower(buf[0]);
            prefix[1] = ':';
            prefix[2] = '/';
            prefix_len = 3;
            i = 3;

            res->is_abs = 1;
            res->volume_len = 1;
            Newx(res->volume, 1, char);
            res->volume[0] = prefix[0];
        }
# endif

        struct Tokens tokens [ (buf_len / 2) + 1 ];
        size_t tokens_len = 0;
        size_t tokens_total_len = 0;

        U8 token[ buf_len ];
        size_t token_len = 0;

        for ( ; i < buf_len; i++ ) {
            int process_token = 0;

            // slash char
            if ( buf[i] == '/' || buf[i] == '\\' ) {
                process_token = 1;
            }
            else {

                // add char to the current token
                token[ token_len++ ] = buf[i];

                // last char
                if (i + 1 == buf_len) process_token = 1;
            }

            // current token is completed, process token
            if (process_token && token_len) {
                int skip_token = 0;
                int is_dots = 0;

                // skip "." token
                if ( token_len == 1 && token[0] == '.' ) {
                    skip_token = 1;
                }

                // process ".." token
                else if ( token_len == 2 && token[0] == '.' && token[1] == '.' ) {
                    is_dots = 1;

                    // has previous token
                    if (tokens_len) {

                        // previous token is NOT "..", remove previous token
                        if (!tokens[tokens_len - 1].is_dots) {
                            skip_token = 1;

                            Safefree(tokens[tokens_len - 1].token);
                            tokens_total_len -= tokens[tokens_len - 1].len;

                            tokens_len -= 1;
                        }
                    }

                    // has no previous token
                    else {

                        // path is absolute, skip ".." token
                        if (prefix_len) skip_token = 1;
                    }
                }

                // store token
                if (!skip_token) {

                    // last token, and token is not "." or ".." or last char is not "/" or "\" - last token is filename
                    if (i + 1 == buf_len && !is_dots && buf[i] != '/' && buf[i] != '\\') {
                        res->filename_len = token_len;
                        Newx(res->filename, token_len, char);
                        memcpy(res->filename, token, token_len);

                        int has_suffix = 0;

                        // parse filename_base, suffix
                        for (size_t i = token_len - 1; i > 0; i--) {

                            // not-leading dot found
                            if (token[i] == '.') {
                                has_suffix = 1;

                                res->suffix_len = token_len - i - 1;
                                Newx(res->suffix, res->suffix_len, char);
                                memcpy(res->suffix, token + i + 1, res->suffix_len);

                                res->filename_base_len = i;
                                Newx(res->filename_base, res->filename_base_len, char);
                                memcpy(res->filename_base, token, res->filename_base_len);

                                break;
                            }
                        }

                        // filename_base = filename if !has_suffix
                        if (!has_suffix) {
                            res->filename_base_len = token_len;
                            Newx(res->filename_base, token_len, char);
                            memcpy(res->filename_base, token, token_len);
                        }
                    }

                    Newx(tokens[tokens_len].token, token_len, U8);
                    memcpy(tokens[tokens_len].token, token, token_len);

                    tokens[tokens_len].len = token_len;
                    tokens[tokens_len].is_dots = is_dots;

                    tokens_total_len += token_len;
                    tokens_len++;
                }

                token_len = 0;
            }
        }

        // calculate path length
        res->path_len = prefix_len + tokens_total_len;
        if (tokens_len) res->path_len += tokens_len - 1;

        // path is not empty
        if (res->path_len) {
            Newx(res->path, res->path_len, char);
            size_t dst_pos = 0;

            // add prefix
            if (prefix_len) {
                dst_pos += prefix_len;
                memcpy(res->path, &prefix, prefix_len);
            }

            // join tokens
            for ( size_t i = 0; i < tokens_len; i++ ) {
                memcpy(res->path + dst_pos, tokens[i].token, tokens[i].len);

                Safefree(tokens[i].token);

                dst_pos += tokens[i].len;

                // add "/" if token is not last
                if (i < tokens_len - 1) res->path[dst_pos++] = '/';
            }

            // path has filename, dirname = path - filename
            if (res->filename_len) {
                res->dirname_len = res->path_len - res->filename_len;
                if (res->dirname_len > 1) res->dirname_len -= 1;

                if(res->dirname_len) {
                    Newx(res->dirname, res->dirname_len, char);
                    Copy(res->path, res->dirname, res->dirname_len, char);
                }
            }

            // path has no filename, dirname = path
            else {
                res->dirname_len = res->path_len;
                Newx(res->dirname, res->dirname_len, char);
                Copy(res->path, res->dirname, res->dirname_len, char);
            }
        }
    }

    if (!res->path_len) {
        res->path_len = 1;
        Newx(res->path, res->path_len, char);
        res->path[0] = '.';
    }

    if (!res->dirname_len) {
        res->dirname_len = 1;
        Newx(res->dirname, res->dirname_len, char);
        res->dirname[0] = '.';
    }

    return res;
}