/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2018 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "platform.h"
#include "path.h"
#include "tinyxml2.h"
#include <cstdlib>
#include <cstring>
#include <limits>
#include <vector>

cppcheck::Platform::Platform()
{
    // This assumes the code you are checking is for the same architecture this is compiled on.
#if defined(_WIN64)
    platform(Win64);
#elif defined(_WIN32)
    platform(Win32A);
#else
    platform(Native);
#endif
}


bool cppcheck::Platform::platform(cppcheck::Platform::PlatformType type)
{
    switch (type) {
    case Unspecified: // unknown type sizes (sizes etc are set but are not known)
    case Native: // same as system this code was compile on
        platformType = type;
        sizeof_bool = sizeof(bool);
        sizeof_short = sizeof(short);
        sizeof_int = sizeof(int);
        sizeof_long = sizeof(long);
        sizeof_long_long = sizeof(long long);
        sizeof_float = sizeof(float);
        sizeof_double = sizeof(double);
        sizeof_long_double = sizeof(long double);
        sizeof_wchar_t = sizeof(wchar_t);
        sizeof_size_t = sizeof(std::size_t);
        sizeof_pointer = sizeof(void *);
        if (type == Unspecified) {
            defaultSign = '\0';
        } else {
            defaultSign = (std::numeric_limits<char>::is_signed) ? 's' : 'u';
        }
        char_bit = 8;
        short_bit = char_bit * sizeof_short;
        int_bit = char_bit * sizeof_int;
        long_bit = char_bit * sizeof_long;
        long_long_bit = char_bit * sizeof_long_long;
        return true;
    case Win32W:
    case Win32A:
        platformType = type;
        sizeof_bool = 1; // 4 in Visual C++ 4.2
        sizeof_short = 2;
        sizeof_int = 4;
        sizeof_long = 4;
        sizeof_long_long = 8;
        sizeof_float = 4;
        sizeof_double = 8;
        sizeof_long_double = 8;
        sizeof_wchar_t = 2;
        sizeof_size_t = 4;
        sizeof_pointer = 4;
        defaultSign = '\0';
        char_bit = 8;
        short_bit = char_bit * sizeof_short;
        int_bit = char_bit * sizeof_int;
        long_bit = char_bit * sizeof_long;
        long_long_bit = char_bit * sizeof_long_long;
        return true;
    case Win64:
        platformType = type;
        sizeof_bool = 1;
        sizeof_short = 2;
        sizeof_int = 4;
        sizeof_long = 4;
        sizeof_long_long = 8;
        sizeof_float = 4;
        sizeof_double = 8;
        sizeof_long_double = 8;
        sizeof_wchar_t = 2;
        sizeof_size_t = 8;
        sizeof_pointer = 8;
        defaultSign = '\0';
        char_bit = 8;
        short_bit = char_bit * sizeof_short;
        int_bit = char_bit * sizeof_int;
        long_bit = char_bit * sizeof_long;
        long_long_bit = char_bit * sizeof_long_long;
        return true;
    case Unix32:
        platformType = type;
        sizeof_bool = 1;
        sizeof_short = 2;
        sizeof_int = 4;
        sizeof_long = 4;
        sizeof_long_long = 8;
        sizeof_float = 4;
        sizeof_double = 8;
        sizeof_long_double = 12;
        sizeof_wchar_t = 4;
        sizeof_size_t = 4;
        sizeof_pointer = 4;
        defaultSign = '\0';
        char_bit = 8;
        short_bit = char_bit * sizeof_short;
        int_bit = char_bit * sizeof_int;
        long_bit = char_bit * sizeof_long;
        long_long_bit = char_bit * sizeof_long_long;
        return true;
    case Unix64:
        platformType = type;
        sizeof_bool = 1;
        sizeof_short = 2;
        sizeof_int = 4;
        sizeof_long = 8;
        sizeof_long_long = 8;
        sizeof_float = 4;
        sizeof_double = 8;
        sizeof_long_double = 16;
        sizeof_wchar_t = 4;
        sizeof_size_t = 8;
        sizeof_pointer = 8;
        defaultSign = '\0';
        char_bit = 8;
        short_bit = char_bit * sizeof_short;
        int_bit = char_bit * sizeof_int;
        long_bit = char_bit * sizeof_long;
        long_long_bit = char_bit * sizeof_long_long;
        return true;
    case PlatformFile:
        // sizes are not set.
        return false;
    }
    // unsupported platform
    return false;
}

bool cppcheck::Platform::loadPlatformFile(const char exename[], const std::string &filename)
{
    // open file..
    tinyxml2::XMLDocument doc;
    if (doc.LoadFile(filename.c_str()) != tinyxml2::XML_SUCCESS) {
        std::vector<std::string> filenames;
        filenames.push_back(filename + ".xml");
        if (exename && (std::string::npos != Path::fromNativeSeparators(exename).find('/'))) {
            filenames.push_back(Path::getPathFromFilename(Path::fromNativeSeparators(exename)) + "platforms/" + filename);
            filenames.push_back(Path::getPathFromFilename(Path::fromNativeSeparators(exename)) + "platforms/" + filename + ".xml");
        }
#ifdef CFGDIR
        std::string cfgdir = CFGDIR;
        if (!cfgdir.empty() && cfgdir[cfgdir.size()-1] != '/')
            cfgdir += '/';
        filenames.push_back(cfgdir + ("../platforms/" + filename));
        filenames.push_back(cfgdir + ("../platforms/" + filename + ".xml"));
#endif
        bool success = false;
        for (int i = 0; i < filenames.size(); ++i) {
            if (doc.LoadFile(filenames[i].c_str()) == tinyxml2::XML_SUCCESS) {
                success = true;
                break;
            }
        }
        if (!success)
            return false;
    }

    return loadFromXmlDocument(&doc);
}

bool cppcheck::Platform::loadFromXmlDocument(const tinyxml2::XMLDocument *doc)
{
    const tinyxml2::XMLElement * const rootnode = doc->FirstChildElement();

    if (!rootnode || std::strcmp(rootnode->Name(), "platform") != 0)
        return false;

    for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) {
        if (std::strcmp(node->Name(), "default-sign") == 0)
            defaultSign = *node->GetText();
        else if (std::strcmp(node->Name(), "char_bit") == 0)
            char_bit = std::atoi(node->GetText());
        else if (std::strcmp(node->Name(), "sizeof") == 0) {
            for (const tinyxml2::XMLElement *sz = node->FirstChildElement(); sz; sz = sz->NextSiblingElement()) {
                if (std::strcmp(sz->Name(), "short") == 0)
                    sizeof_short = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "bool") == 0)
                    sizeof_bool = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "int") == 0)
                    sizeof_int = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "long") == 0)
                    sizeof_long = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "long-long") == 0)
                    sizeof_long_long = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "float") == 0)
                    sizeof_float = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "double") == 0)
                    sizeof_double = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "long-double") == 0)
                    sizeof_long_double = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "pointer") == 0)
                    sizeof_pointer = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "size_t") == 0)
                    sizeof_size_t = std::atoi(sz->GetText());
                else if (std::strcmp(sz->Name(), "wchar_t") == 0)
                    sizeof_wchar_t = std::atoi(sz->GetText());
            }
        }
    }

    short_bit = char_bit * sizeof_short;
    int_bit = char_bit * sizeof_int;
    long_bit = char_bit * sizeof_long;
    long_long_bit = char_bit * sizeof_long_long;

    platformType = PlatformFile;
    return true;
}