// $Id: system.cpp,v 1.6 1999/02/12 14:39:13 shields Exp $
copyright notice

#include "config.h"
#include <sys/stat.h>
#include "control.h"
#include "semantic.h"
#include "zip.h"

int Control::ConvertUnicodeToUtf8(wchar_t *source, char *target)
{
    int length = 0;

    for (; *source; source++)
    {
        int ch = *source;

        if (ch == 0)
        {
             target[length++] = (char) 0xC0;
             target[length++] = (char) 0x80;
        }
        else if (ch <= 0x007F)
             target[length++] = (char) ch;
        else if (ch <= 0x07FF)
        {
             target[length++] = (char) ((char) 0xC0 | (char) ((ch >> 6) & 0x001F)); // bits 6-10
             target[length++] = (char) ((char) 0x80 | (char) (ch & 0x003F));        // bits 0-5
        }
        else
        {
             target[length++] = (char) ((char) 0xE0 | (char) ((ch >> 12) & 0x000F));
             target[length++] = (char) ((char) 0x80 | (char) ((ch >> 6) & 0x003F));
             target[length++] = (char) ((char) 0x80 | (char) (ch & 0x3F));
        }
    }
    target[length] = U_NULL;

    return length;
}


void Control::FindPathsToDirectory(PackageSymbol *package)
{
    if (package -> directory.Length() == 0)
    {
        PackageSymbol *owner_package = package -> owner;
        if (owner_package) // package is a subpackage?
        {
            for (int i = 0; i < owner_package -> directory.Length(); i++)
            {
                DirectorySymbol *owner_directory_symbol = owner_package -> directory[i],
                                *subdirectory_symbol = owner_directory_symbol -> FindDirectorySymbol(package -> Identity());
                if ((! subdirectory_symbol) && (! owner_directory_symbol -> IsZip()))
                {
                    int length = owner_directory_symbol -> DirectoryNameLength() + package -> Utf8NameLength() + 1; // +1 for '/'
                    char *directory_name = new char[length + 1]; // +1 for '\0';

                    strcpy(directory_name, owner_directory_symbol -> DirectoryName());
                    if (owner_directory_symbol -> DirectoryName()[owner_directory_symbol -> DirectoryNameLength() - 1] != U_SLASH)
                        strcat(directory_name, StringConstant::U8S__SL_);
                    strcat(directory_name, package -> Utf8Name());

                    if (::SystemIsDirectory(directory_name))
                        subdirectory_symbol = owner_directory_symbol -> InsertAndReadDirectorySymbol(package -> Identity());

                    delete [] directory_name;
                }

                if (subdirectory_symbol)
                    package -> directory.Next() = subdirectory_symbol;
            }
        }
        else
        {
            //
            // Recall that since classpath[0] contains the default directory, we always
            // start searching at location 1.
            //
            for (int k = 1; k < classpath.Length(); k++)
            {
                PathSymbol *path_symbol = classpath[k];
                DirectorySymbol *directory_symbol = path_symbol -> RootDirectory() -> FindDirectorySymbol(package -> Identity());
                if ((! directory_symbol) && (! path_symbol -> IsZip()))
                {
                    int length = path_symbol -> Utf8NameLength() + package -> Utf8NameLength() + 1; // +1 for '/'
                    char *directory_name = new char[length + 1]; // +1 for '/' +1 for '\0'
                    strcpy(directory_name, path_symbol -> Utf8Name());
                    strcat(directory_name, StringConstant::U8S__SL_);
                    strcat(directory_name, package -> Utf8Name());

                    if (::SystemIsDirectory(directory_name))
                        directory_symbol = path_symbol -> RootDirectory() -> InsertAndReadDirectorySymbol(package -> Identity());
                    delete [] directory_name;
                }

                if (directory_symbol)
                    package -> directory.Next() = directory_symbol;
            }
        }
    }

    return;
}


void Control::ProcessGlobalNameSymbols()
{Initialize top level variables in the Control class, 
 adding a set of names to the global NameLookupTable

            dot_name_symbol '.' is the "current directory"
    this -> dot_name_symbol        = FindOrInsertName(US__DO_, wcslen(US__DO_));
            dot_dot_name_symbol '..' is the parent directory
    this -> dot_dot_name_symbol    = FindOrInsertName(US__DO__DO_, wcslen(US__DO__DO_));
            length_name_symbol 'length' is the builtin array length method
    this -> length_name_symbol     = FindOrInsertName(US_length, wcslen(US_length));
            init_name_symbol '<init>' is the class constructor's internal name
    this -> init_name_symbol       = FindOrInsertName(US__LT_init_GT_, wcslen(US__LT_init_GT_));
            clinit_name_symbol '<clinit>' is the class static initializer name
    this -> clinit_name_symbol     = FindOrInsertName(US__LT_clinit_GT_, wcslen(US__LT_clinit_GT_));
            block_init_name_symbol 'block$' is the block initializer name
    this -> block_init_name_symbol = FindOrInsertName(US_block_DOLLAR, wcslen(US_block_DOLLAR));
            this0_name_symbol 'this$0' is the 'this' name in inner classes
    this -> this0_name_symbol      = FindOrInsertName(US_this0, wcslen(US_this0));
            clone_name_symbol 'clone' is the array clone symbol
    this -> clone_name_symbol      = FindOrInsertName(US_clone, wcslen(US_clone));
            object_name_symbol 'Object' is the name of the root of the class hierarchy
    this -> object_name_symbol     = FindOrInsertName(US_Object, wcslen(US_Object));
            type_name_symbol 'TYPE' appears to be associated with non-primitive type casts
    this -> type_name_symbol       = FindOrInsertName(US_TYPE, wcslen(US_TYPE));
            class_name_symbol 'class$' is not used
    this -> class_name_symbol      = FindOrInsertName(US__class_DOLLAR, wcslen(US__class_DOLLAR));

    return;
}


//
// Create the unnamed package and set up global names.
//
void Control::ProcessUnnamedPackage()
{unnamed_package is the symbol "" (a null string).
    unnamed_package = external_table.InsertPackageSymbol(FindOrInsertName(US_EMPTY, wcslen(US_EMPTY)), NULL);

    //
    // Create an entry for no_type. no_type is used primarily to signal an errorus_
    //
    no_type = unnamed_package -> InsertSystemTypeSymbol(FindOrInsertName(US__QU__QU_, wcslen(US__QU__QU_)));
    no_type -> SetSignature(Utf8_pool.FindOrInsert(U8S__DO_, strlen(U8S__DO_))); // give it some signature...
    no_type -> outermost_type = no_type;
    no_type -> SetOwner(unnamed_package);
    no_type -> MarkBad();

    //
    // Create an entry for the null type.
    //
    null_type = unnamed_package -> InsertSystemTypeSymbol(FindOrInsertName(US_null, wcslen(US_null)));
    null_type -> outermost_type = null_type;
    null_type -> SetOwner(unnamed_package);
    null_type -> SetACC_PUBLIC();

    return;
}


void Control::ProcessPath()
{
    NameSymbol *dot_path_name_symbol;

#ifdef UNIX_FILE_SYSTEM
    dot_path_name_symbol = dot_name_symbol;
#elif defined(WIN32_FILE_SYSTEM)
    char *main_current_directory = option.GetMainCurrentDirectory();
    int dot_path_name_length = strlen(main_current_directory);
    wchar_t *dot_path_name = new wchar_t[dot_path_name_length + 1];
    for (int i = 0; i < dot_path_name_length; i++)
        dot_path_name[i] = main_current_directory[i];
    dot_path_name[dot_path_name_length] = U_NULL;
    dot_path_name_symbol = FindOrInsertName(dot_path_name, dot_path_name_length);
    delete [] dot_path_name;
#endif

    //
    // We need a place to start. Allocate a "." directory with no owner initially. (Hence, the null argument.)
    // Allocate a "." path whose associated directory is the "." directory.
    // Identify the "." path as the owner of the "." directory.
    //
    DirectorySymbol *dot_directory = new DirectorySymbol(dot_name_symbol, NULL);
    classpath.Next() = classpath_table.InsertPathSymbol(dot_path_name_symbol, dot_directory);
    dot_directory -> ResetDirectory(); // Note that main_root_directory is reset after it has been assigned an owner above
    root_directories.Next() = dot_directory;

    //
    //
    //
    if (option.classpath)
    {
        int max_path_name_length = strlen(option.classpath) + 1; // The longest possible path name we can encounter
        wchar_t *path_name = new wchar_t[max_path_name_length + 1]; // +1 for '\0'

        wchar_t *input_name = NULL;
        char *full_directory_name = NULL;

        for (char *path = option.classpath, *path_tail = &path[strlen(path)]; path < path_tail; path++)
        {
#ifdef WIN32_FILE_SYSTEM
            delete [] full_directory_name;
            delete [] input_name;
#endif
            char *head;
            for (head = path; path < path_tail && *path != ::PathSeparator(); path++)
                ;
            *path = U_NULL; // If a seperator was encountered, replace it by \0 to terminate the string.
            int input_name_length = path - head;

            int path_name_length = input_name_length;
            for (int i = 0; i < path_name_length; i++)
                path_name[i] = head[i];
            path_name[path_name_length] = U_NULL;

#ifdef UNIX_FILE_SYSTEM

            input_name = path_name;

#elif defined(WIN32_FILE_SYSTEM)

            input_name = NULL;
            full_directory_name = NULL;
            char disk = (input_name_length >= 2 && Case::IsAsciiAlpha(head[0]) && head[1] == U_COLON ? head[0] : 0);

            //
            // Look for the directory. If it is found, update input_name and head.
            //
            option.SaveCurrentDirectoryOnDisk(disk);
            if (SetCurrentDirectory(head))
            {
                char tmp[1];
                DWORD directory_length = GetCurrentDirectory(0, tmp); // first, get the right size
                full_directory_name = new char[directory_length + 1];  // allocate the directory
                DWORD length = GetCurrentDirectory(directory_length, full_directory_name);
                if (length <= directory_length)
                {
                    for (char *ptr = full_directory_name; *ptr; ptr++)
                        *ptr = (*ptr != U_BACKSLASH ? *ptr : (char) U_SLASH); // turn '\' to '/'.

                    input_name_length = length;
                    input_name = new wchar_t[input_name_length + 1];
                    for (int k = 0; k < input_name_length; k++)
                        input_name[k] = full_directory_name[k];
                    input_name[input_name_length] = U_NULL;
                    head = full_directory_name;
                }
            }

            //
            // Default input_name, in case we do not succeed in finding the directory
            //
            if (! input_name)
            {
                input_name = new wchar_t[input_name_length + 1];
                for (int j = 0; j < input_name_length; j++)
                    input_name[j] = path_name[j];
                input_name[input_name_length] = U_NULL;
            }

            option.ResetCurrentDirectoryOnDisk(disk); // reset the current directory on disk
            option.SetMainCurrentDirectory();         // reset the real current directory...
#endif

            //
            //
            //
            if (input_name_length > 0)
            {
                NameSymbol *name_symbol = FindOrInsertName(path_name, path_name_length);

                //
                // If a directory is specified more than once, ignore the duplicates.
                //
                PathSymbol *path_symbol = classpath_table.FindPathSymbol(name_symbol);
                if (path_symbol)
                {
                    if (name_symbol == dot_name_symbol)
                    {
                        dot_classpath_index = classpath.Length(); // The next index
                        classpath.Next() = classpath[0];          // share the "." directory
                        unnamed_package -> directory.Next() = classpath[0] -> RootDirectory();
                    }

                    continue;
                }

                //
                // Check whether or not the path points to a system directory or a zip file
                //
                Zip *zipinfo = (::SystemIsDirectory(head) ? (Zip *) NULL : new Zip(*this, head));

                //
                // If we have an invalid zip file issue an error and continue...
                //
                DirectorySymbol *dot_directory;
                if (zipinfo)
                {
                     if (! zipinfo -> IsValid())
                     {
                         wchar_t *name = new wchar_t[input_name_length + 1];
                         for (int i = 0; i < input_name_length; i++)
                             name[i] = input_name[i];
                         name[input_name_length] = U_NULL;
                         bad_zip_filenames.Next() = name;
   
                         delete zipinfo;
                         continue;
                     }

                     dot_directory = new DirectorySymbol(dot_name_symbol, NULL);
                     root_directories.Next() = dot_directory;
                }
                else dot_directory = ProcessSubdirectories(input_name, input_name_length);

                //
                // Create the new path symbol and update the class path with it.
                //
                path_symbol = classpath_table.InsertPathSymbol(name_symbol, dot_directory);
                classpath.Next() = path_symbol;

                //
                // If the path symbol is associated with a zip file, read the zip directory.
                //
                if (zipinfo)
                {
                    path_symbol -> zipfile = zipinfo;
                    zipinfo -> ReadDirectory(dot_directory);
                }

                unnamed_package -> directory.Next() = dot_directory;
            }
        }

#ifdef WIN32_FILE_SYSTEM
        delete [] full_directory_name;
        delete [] input_name;
#endif

        delete [] path_name;
    }

    //
    // TODO: If the user did not specify "." in the class path we assume it.
    // javac makes that assumption also. Is that correct?
    //
    if (dot_classpath_index == 0)
        unnamed_package -> directory.Next() = classpath[0] -> RootDirectory();

    return;
}


TypeSymbol *Control::GetPrimitiveType(wchar_t *name, char *signature)
{
    NameSymbol *name_symbol = FindOrInsertName(name, wcslen(name));
    TypeSymbol *type = unnamed_package -> InsertSystemTypeSymbol(name_symbol);

    type -> SetSignature(Utf8_pool.FindOrInsert(signature, strlen(signature)));
    type -> outermost_type = type;
    type -> SetOwner(unnamed_package);
    type -> SetACC_PUBLIC();
    type -> MarkPrimitive();

    return type;
}


void Control::ProcessSystemInformation()
{
    //
    // Add entry for system package
    //
    system_package = ProcessPackage(StringConstant::US_java_SL_lang);

    //
    // Create an entry for each primitive type. Note that the type void is treated as a primitive.
    //
    void_type = GetPrimitiveType(US_void, U8S_V);
    boolean_type = GetPrimitiveType(US_boolean, U8S_Z);
    byte_type = GetPrimitiveType(US_byte, U8S_B);
    char_type = GetPrimitiveType(US_char, U8S_C);
    short_type = GetPrimitiveType(US_short, U8S_S);
    int_type = GetPrimitiveType(US_int, U8S_I);
    long_type = GetPrimitiveType(US_long, U8S_J);
    float_type = GetPrimitiveType(US_float, U8S_F);
    double_type = GetPrimitiveType(US_double, U8S_D);

    return;
}    


DirectorySymbol *Control::GetOutputDirectory(FileSymbol *file_symbol)
{
    DirectorySymbol *directory_symbol;

    Control &control = file_symbol -> semantic -> control;
    if (control.option.directory == NULL)
        directory_symbol = file_symbol -> directory_symbol;
    else
    {
        char *directory_prefix = control.option.directory;
        int directory_prefix_length = strlen(directory_prefix),
            utf8_name_length = file_symbol -> package -> PackageNameLength() * 3,
            estimated_length = directory_prefix_length + utf8_name_length + 1; // +1 for slash

        char *directory_name = new char[estimated_length + 1]; // +1 for '\0'

        strcpy(directory_name, directory_prefix);

        if (file_symbol -> package != control.unnamed_package) // if there was a package declaration, then...
        {
            strcat(directory_name, StringConstant::U8S__SL_);
            char *utf8_name = new char[utf8_name_length + 1];
            (void) ConvertUnicodeToUtf8(file_symbol -> package -> PackageName(), utf8_name);
            strcat(directory_name, utf8_name);
            delete [] utf8_name;

            if (! ::SystemIsDirectory(directory_name)) // The directory does not yet exist. 
            {
                for (char *ptr = &directory_name[directory_prefix_length + 1]; *ptr; ptr++)
                {
                    if (*ptr == U_SLASH) // all the slashes in a package_name are forward slashes
                    {
                        *ptr = U_NULL;
                        if (! ::SystemIsDirectory(directory_name))
                            ::SystemMkdir(directory_name);
                        *ptr = U_SLASH; // restore slash
                    }
                }
                ::SystemMkdir(directory_name);
            }
        }

        //
        // Read the directory and insert the directory symbol. Note that since 
        // the original length computation was an estimate, we compute the real
        // length here.
        //
        int length = strlen(directory_name);
        wchar_t *name = new wchar_t[length + 1];
        for (int i = 0; i < length; i++)
            name[i] = directory_name[i];
        name[length] = U_NULL;

        directory_symbol = control.ProcessSubdirectories(name, length);

        delete [] directory_name;
    }

    return directory_symbol;
}


FileSymbol *Control::GetJavaFile(PackageSymbol *package, NameSymbol *name_symbol)
{
    FileSymbol *file_symbol = NULL;

    //
    //
    //
    int length = name_symbol -> Utf8NameLength() + FileSymbol::java_suffix_length;
    char *full_filename = new char[length + 1]; // +1 for \0
    strcpy(full_filename, name_symbol -> Utf8Name());
    strcat(full_filename, FileSymbol::java_suffix);

    DirectoryEntry *entry = NULL;
    DirectorySymbol *directory_symbol = NULL;
    for (int k = 0; k < package -> directory.Length(); k++)
    {
        directory_symbol = package -> directory[k];
        if (entry = directory_symbol -> FindEntry(full_filename, length))
            break;
    }

    if (entry)
    {
        PathSymbol *path_symbol = directory_symbol -> PathSym();

        file_symbol = directory_symbol -> FindFileSymbol(name_symbol);
        if (! ((file_symbol && file_symbol -> IsJava()) || path_symbol -> IsZip()))
        {
            file_symbol = directory_symbol -> InsertFileSymbol(name_symbol);

            file_symbol -> directory_symbol = directory_symbol;
            file_symbol -> SetJava();
            file_symbol -> mtime = entry -> Mtime();
        }
    }

    delete [] full_filename;

    return file_symbol;
}


FileSymbol *Control::GetFile(PackageSymbol *package, NameSymbol *name_symbol, bool depend)
{
    FileSymbol *file_symbol = NULL;

    //
    // calculate a length that is large enough...
    //
    int class_length = name_symbol -> Utf8NameLength() + FileSymbol::class_suffix_length,
        java_length = name_symbol -> Utf8NameLength() + FileSymbol::java_suffix_length;

    char *class_name = new char[class_length + 1]; // +1 for \0
    strcpy(class_name, name_symbol -> Utf8Name());
    strcat(class_name, FileSymbol::class_suffix);

    char *java_name = new char[java_length + 1]; // +1 for \0
    strcpy(java_name, name_symbol -> Utf8Name());
    strcat(java_name, FileSymbol::java_suffix);

    for (int k = 0; k < package -> directory.Length(); k++)
    {
        DirectorySymbol *directory_symbol = package -> directory[k];
        file_symbol = directory_symbol -> FindFileSymbol(name_symbol);
        if (file_symbol)
             break;

        PathSymbol *path_symbol = directory_symbol -> PathSym();
        if (! path_symbol -> IsZip())
        {
            DirectoryEntry *java_entry = directory_symbol -> FindEntry(java_name, java_length),
                           *class_entry = (((! depend) || (java_entry == NULL))
                                                        ? directory_symbol -> FindEntry(class_name, class_length)
                                                        : (DirectoryEntry *) NULL);

            if (java_entry || class_entry)
            {
                file_symbol = directory_symbol -> InsertFileSymbol(name_symbol);
                file_symbol -> directory_symbol = directory_symbol;
                
                if (java_entry && ((! class_entry) || class_entry -> Mtime() < java_entry -> Mtime()))
                {
                    file_symbol -> SetJava();
                    file_symbol -> mtime = java_entry -> Mtime();
                }
                else
                {
                    if (java_entry)
                         file_symbol -> SetClass();
                    else file_symbol -> SetClassOnly();
                    file_symbol -> mtime = class_entry -> Mtime();
                }
                break;
            }
        }
    }

    delete [] java_name;
    delete [] class_name;

    return file_symbol;
}


TypeSymbol *Control::GetType(PackageSymbol *package, wchar_t *name)
{
    NameSymbol *name_symbol = FindOrInsertName(name, wcslen(name));
    TypeSymbol *type = package -> FindTypeSymbol(name_symbol);

    if (! type)
    {
        FileSymbol *file_symbol = GetFile(package, name_symbol, option.depend);
        type = system_semantic -> ReadType(file_symbol, package, name_symbol, 0);
    }
    else if (type -> SourcePending())
         ProcessHeaders(type -> file_symbol);

    return type;
}