/*
 * Copyright 2010, The Android Open Source Project
 *
 * Licensed 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 "slang_rs_reflect_utils.h"

#include <cstdio>
#include <cstring>
#include <string>

#include "llvm/ADT/StringRef.h"

#include "os_sep.h"
#include "slang_utils.h"

namespace slang {

using std::string;

string RSSlangReflectUtils::GetFileNameStem(const char* fileName) {
    const char *dot = fileName + strlen(fileName);
    const char *slash = dot - 1;
    while (slash >= fileName) {
        if (*slash == OS_PATH_SEPARATOR) {
            break;
        }
        if ((*slash == '.') && (*dot == 0)) {
            dot = slash;
        }
        --slash;
    }
    ++slash;
    return string(slash, dot - slash);
}

string RSSlangReflectUtils::ComputePackagedPath(
    const char *prefixPath, const char *packageName) {
    string packaged_path(prefixPath);
    if (!packaged_path.empty() &&
        (packaged_path[packaged_path.length() - 1] != OS_PATH_SEPARATOR)) {
        packaged_path += OS_PATH_SEPARATOR_STR;
    }
    size_t s = packaged_path.length();
    packaged_path += packageName;
    while (s < packaged_path.length()) {
        if (packaged_path[s] == '.') {
            packaged_path[s] = OS_PATH_SEPARATOR;
        }
        ++s;
    }
    return packaged_path;
}

static string InternalFileNameConvert(const char *rsFileName, bool toLower) {
    const char *dot = rsFileName + strlen(rsFileName);
    const char *slash = dot - 1;
    while (slash >= rsFileName) {
        if (*slash == OS_PATH_SEPARATOR) {
            break;
        }
        if ((*slash == '.') && (*dot == 0)) {
            dot = slash;
        }
        --slash;
    }
    ++slash;
    char ret[256];
    int i = 0;
    for (; (i < 255) && (slash < dot); ++slash) {
        if (isalnum(*slash) || *slash == '_') {
            if (toLower) {
                ret[i] = tolower(*slash);
            } else {
                ret[i] = *slash;
            }
            ++i;
        }
    }
    ret[i] = 0;
    return string(ret);
}

std::string RSSlangReflectUtils::JavaClassNameFromRSFileName(
    const char *rsFileName) {
    return InternalFileNameConvert(rsFileName, false);
}


std::string RSSlangReflectUtils::BCFileNameFromRSFileName(
    const char *rsFileName) {
    return InternalFileNameConvert(rsFileName, true);
}

static bool GenerateAccessorHeader(
    const RSSlangReflectUtils::BitCodeAccessorContext &context, FILE *pfout) {
    fprintf(pfout, "/*\n");
    fprintf(pfout, " * This file is auto-generated. DO NOT MODIFY!\n");
    fprintf(pfout, " * The source Renderscript file: %s\n", context.rsFileName);
    fprintf(pfout, " */\n\n");
    fprintf(pfout, "package %s;\n\n", context.packageName);

    // add imports here.

    return true;
}

static bool GenerateAccessorMethodSignature(
    const RSSlangReflectUtils::BitCodeAccessorContext &context, FILE *pfout) {
    // the prototype of the accessor method
    fprintf(pfout, "  // return byte array representation of the bitcode.\n");
    fprintf(pfout, "  public static byte[] getBitCode() {\n");
    return true;
}

// Java method size must not exceed 64k,
// so we have to split the bitcode into multiple segments.
static bool GenerateSegmentMethod(
    const char *buff, int blen, int seg_num, FILE *pfout) {

    fprintf(pfout, "  private static byte[] getSegment_%d() {\n", seg_num);
    fprintf(pfout, "    byte[] data = {\n");

    static const int LINE_BYTE_NUM = 16;
    char out_line[LINE_BYTE_NUM*6 + 10];
    const char *out_line_end = out_line + sizeof(out_line);
    char *p = out_line;

    int write_length = 0;
    while (write_length < blen) {
        p += snprintf(p, out_line_end - p,
                      " %4d,", static_cast<int>(buff[write_length]));
        ++write_length;
        if (((write_length % LINE_BYTE_NUM) == 0)
            || (write_length == blen)) {
          fprintf(pfout, "     ");
          fprintf(pfout, "%s", out_line);
          fprintf(pfout, "\n");
          p = out_line;
        }
    }

    fprintf(pfout, "    };\n");
    fprintf(pfout, "    return data;\n");
    fprintf(pfout, "  }\n\n");

    return true;
}

static bool GenerateJavaCodeAccessorMethod(
    const RSSlangReflectUtils::BitCodeAccessorContext &context, FILE *pfout) {
    FILE *pfin = fopen(context.bcFileName, "rb");
    if (pfin == NULL) {
        fprintf(stderr, "Error: could not read file %s\n", context.bcFileName);
        return false;
    }

    // start the accessor method
    GenerateAccessorMethodSignature(context, pfout);
    fprintf(pfout, "    return getBitCodeInternal();\n");
    // end the accessor method
    fprintf(pfout, "  };\n\n");

    // output the data
    // make sure the generated function for a segment won't break the Javac
    // size limitation (64K).
    static const int SEG_SIZE = 0x2000;
    char *buff = new char[SEG_SIZE];
    int read_length;
    int seg_num = 0;
    int total_length = 0;
    while ((read_length = fread(buff, 1, SEG_SIZE, pfin)) > 0) {
        GenerateSegmentMethod(buff, read_length, seg_num, pfout);
        ++seg_num;
        total_length += read_length;
    }
    delete []buff;
    fclose(pfin);

    // output the internal accessor method
    fprintf(pfout, "  private static int bitCodeLength = %d;\n\n",
        total_length);
    fprintf(pfout, "  private static byte[] getBitCodeInternal() {\n");
    fprintf(pfout, "    byte[] bc = new byte[bitCodeLength];\n");
    fprintf(pfout, "    int offset = 0;\n");
    fprintf(pfout, "    byte[] seg;\n");
    for (int i = 0; i < seg_num; ++i) {
    fprintf(pfout, "    seg = getSegment_%d();\n", i);
    fprintf(pfout, "    System.arraycopy(seg, 0, bc, offset, seg.length);\n");
    fprintf(pfout, "    offset += seg.length;\n");
    }
    fprintf(pfout, "    return bc;\n");
    fprintf(pfout, "  }\n\n");

    return true;
}

static bool GenerateAccessorClass(
    const RSSlangReflectUtils::BitCodeAccessorContext &context,
    const char *clazz_name, FILE *pfout) {
    // begin the class.
    fprintf(pfout, "/**\n");
    fprintf(pfout, " * @hide\n");
    fprintf(pfout, " */\n");
    fprintf(pfout, "public class %s {\n", clazz_name);
    fprintf(pfout, "\n");

    bool ret = true;
    switch (context.bcStorage) {
      case BCST_APK_RESOURCE:
        break;
      case BCST_JAVA_CODE:
        ret = GenerateJavaCodeAccessorMethod(context, pfout);
        break;
      default:
        ret = false;
    }

    // end the class.
    fprintf(pfout, "}\n");

    return ret;
}


bool RSSlangReflectUtils::GenerateBitCodeAccessor(
    const BitCodeAccessorContext &context) {
    string output_path = ComputePackagedPath(context.reflectPath,
                                             context.packageName);
    if (!SlangUtils::CreateDirectoryWithParents(llvm::StringRef(output_path),
                                                NULL)) {
        fprintf(stderr, "Error: could not create dir %s\n",
                output_path.c_str());
        return false;
    }

    string clazz_name(JavaClassNameFromRSFileName(context.rsFileName));
    clazz_name += "BitCode";
    string filename(clazz_name);
    filename += ".java";

    string output_filename(output_path);
    output_filename += OS_PATH_SEPARATOR_STR;
    output_filename += filename;
    printf("Generating %s ...\n", filename.c_str());
    FILE *pfout = fopen(output_filename.c_str(), "w");
    if (pfout == NULL) {
        fprintf(stderr, "Error: could not write to file %s\n",
                output_filename.c_str());
        return false;
    }

    bool ret = GenerateAccessorHeader(context, pfout) &&
               GenerateAccessorClass(context, clazz_name.c_str(), pfout);

    fclose(pfout);
    return ret;
}
}  // namespace slang