// Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
//
// WSO2 Inc. 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.

import ballerina/file;
import ballerina/filepath;
import ballerina/http;
import ballerina/log;
import ballerina/mime;

//// Configuration related properties.
boolean isSinglePageApplication = true;

// file extension mapping to content types
final map<string> MIME_MAP = {
    "json": mime:APPLICATION_JSON,
    "xml": mime:TEXT_XML,
    balo: mime:APPLICATION_OCTET_STREAM,
    css: "text/css",
    gif: "image/gif",
    html: mime:TEXT_HTML,
    ico: "image/x-icon",
    jpeg: "image/jpeg",
    jpg: "image/jpeg",
    js: "application/javascript",
    png: "image/png",
    svg: "image/svg+xml",
    txt: mime:TEXT_PLAIN,
    woff2: "font/woff2",
    zip: "application/zip"
};

@http:ServiceConfig {
    basePath: "/",
    chunking: http:CHUNKING_NEVER
}
service webServerSvc on new http:Listener(9090) {
    @http:ResourceConfig {
        methods: ["GET"],
        path: "/*"
    }
    resource function serveHtmlFiles(http:Caller caller, http:Request request) {
        http:Response res = new;
        string requestedFilePath = <@untainted> <string> request.extraPathInfo;
        requestedFilePath = checkpanic filepath:build("app", requestedFilePath);
        
        // If requested for a file(not directory), check if its available in the server and response.
        if (file:exists(requestedFilePath) && isFile(requestedFilePath)) {
            res = getFileAsResponse(requestedFilePath);
            error? clientResponse = caller->respond(res);
            if (clientResponse is error) {
                log:printError("unable respond back", err = clientResponse);
            }
            return;
        }

        // If requested is a directory, check and append forward slash.
        if (!requestedFilePath.endsWith("/")) {
            if (requestedFilePath.length() > 0) {
                requestedFilePath = requestedFilePath + "/";
            }
        }

        // Resolve to index.html of the requested directory
        requestedFilePath = requestedFilePath + "index.html"; // resolve to index.html if not file it found.

        if (isSinglePageApplication && !file:exists(requestedFilePath)) {
            res = getFileAsResponse(checkpanic filepath:build("app", "index.html"));
        } else {
            res = getFileAsResponse(requestedFilePath);
        }

        error? clientResponse = caller->respond(res);
        if (clientResponse is error) {
            log:printError("unable respond back", err = clientResponse);
        }
    }

    @http:ResourceConfig {
        methods: ["GET"],
        path: "/static/*"
    }
    resource function serveStaticFiles(http:Caller caller, http:Request request) {
        // TODO: properly untaint the request.rawPath
        string|error requestedFilePath = filepath:build("app", <@untainted> <string> request.rawPath);
        http:Response res = new;
        if (requestedFilePath is string) {
            res = getFileAsResponse(requestedFilePath);
        } else {
            res.setTextPayload("server error occurred.");
            res.statusCode = 500;
            log:printError("unable to resolve file path", err = requestedFilePath);
        }

        error? clientResponse = caller->respond(res);
        if (clientResponse is error) {
            log:printError("unable respond back", err = clientResponse);
        }
    }
}

# Serve a file as a http response.
#
# + requestedFile - The path of the file to server.
# + return - The http response.
function getFileAsResponse(string requestedFile) returns http:Response {
    http:Response res = new;

    // Figure out content-type
    string contentType = mime:APPLICATION_OCTET_STREAM;
    string fileExtension = checkpanic filepath:extension(requestedFile);
    if (fileExtension != "") {
        contentType = getMimeTypeByExtension(fileExtension);
    }

    // Check if file exists.
    if (file:exists(requestedFile)) {
        res.setFileAsPayload(requestedFile, contentType = contentType);
    } else {
        log:printError("unable to find file: " + requestedFile);
        res.setTextPayload("the server was not able to find what you were looking for.");
        res.statusCode = http:STATUS_NOT_FOUND;
    }
    return res;
}

# Get the content type using a file extension.
#
# + extension - The file extension.
# + return - The content type if a match is found, else application/octet-stream.
function getMimeTypeByExtension(string extension) returns string {
    var contentType = MIME_MAP[extension.toLowerAscii()];
    if (contentType is string) {
        return contentType;
    } else {
        return mime:APPLICATION_OCTET_STREAM;
    }
}

# Check if a file path is a file and not a directory.
# 
# + filePath - Path to the file
# + return - True if file, else false
function isFile(string filePath) returns boolean {
    return !(file:readDir(filePath) is file:FileInfo[]);
}