Viewer::Viewer(const char* title, const FilePath& applicationPath, const FilePath& settingsFilePath):
    m_Path(settingsFilePath.directory()),
    m_Settings(applicationPath.file(), settingsFilePath),
    m_WindowManager(m_Settings.m_WindowSize.x, m_Settings.m_WindowSize.y,
                    title),
    m_GUI(applicationPath.file(), m_WindowManager),
    m_ShaderManager(applicationPath.directory() + "shaders"),
    m_GLImageRenderer(m_ShaderManager),
    m_ViewController(m_WindowManager.getWindow()) {

    std::clog << "applicationPath = " << applicationPath << std::endl;
    std::clog << "settingsFilePath = " << settingsFilePath << std::endl;

    initScene();
    initConfigManager();

    m_ViewController.setSpeed(m_Settings.m_fSpeedFarRatio * length(size(m_pScene->getBBox())));

    std::clog << "Number of system threads = " << getSystemThreadCount() << std::endl;

    m_RenderModule.setUp(m_Path,
                         m_Settings.m_pCacheRoot,
                         m_Settings.m_FramebufferSize);

    {
        // Load current config
        auto pConfig = m_Settings.m_pCacheRoot->FirstChildElement("Config");
        if(pConfig) {
            std::string name;
            if(getAttribute(*pConfig, "name", name)) {
                try {
                    ProjectiveCamera camera;
                    m_ConfigManager.loadConfig(name, camera, *m_pScene,
                        m_Settings.m_FramebufferSize.x, m_Settings.m_FramebufferSize.y,
                        m_ZNearFar.x, m_ZNearFar.y);
                    m_nCurrentConfig = m_ConfigManager.getConfigIndex(name);
                } catch(...) {
                    std::cerr << "Unable to load previously cached configuration" << std::endl;
                }
            }
        } else {
            auto configCount = m_ConfigManager.getConfigs().size();
            if(configCount > 0u) {
                ProjectiveCamera camera;
                m_ConfigManager.loadConfig(0, camera, *m_pScene,
                    m_Settings.m_FramebufferSize.x, m_Settings.m_FramebufferSize.y,
                    m_ZNearFar.x, m_ZNearFar.y);
                m_nCurrentConfig = 0;
            } else {
                LOG(INFO) << "No configs for the scene, the program might eventually crash.";
            }
        }
    }
}
Viewer::Settings::Settings(const std::string& applicationName, const FilePath& filepath):
    m_FilePath(filepath) {

    if(tinyxml2::XML_NO_ERROR != m_Document.LoadFile(filepath.c_str())) {
        throw std::runtime_error("Unable to load viewer settings file");
    }

    if(nullptr == (m_pRoot = m_Document.RootElement())) {
        throw std::runtime_error("Invalid viewer settings file format (no root element)");
    }

    auto pWindow = m_pRoot->FirstChildElement("Window");
    if(!pWindow) {
        throw std::runtime_error("Invalid viewer settings file format (no Window element)");
    }

    if(!getAttribute(*pWindow, "width", m_WindowSize.x) ||
            !getAttribute(*pWindow, "height", m_WindowSize.y)) {
        throw std::runtime_error("Invalid viewer settings file format (no width/height params)");
    }

    auto pViewController = m_pRoot->FirstChildElement("ViewController");
    if(pViewController) {
        if(!getAttribute(*pViewController, "speedFarRatio", m_fSpeedFarRatio)) {
            throw std::runtime_error("Invalid viewer settings file format (no speedFarRatio params in ViewController)");
        }
    }

    auto pFramebuffer = m_pRoot->FirstChildElement("Framebuffer");
    if(!getAttribute(*pFramebuffer, "width", m_FramebufferSize.x) ||
            !getAttribute(*pFramebuffer, "height", m_FramebufferSize.y)) {
        throw std::runtime_error("Invalid viewer settings file format (no width/height params for the framebuffer)");
    }
    m_fFramebufferRatio = float(m_FramebufferSize.x) / m_FramebufferSize.y;

    auto cacheDirectory = filepath.directory() + "cache";
    createDirectory(cacheDirectory);
    m_CacheFilePath = cacheDirectory + (applicationName + ".cache.bnz.xml");
    if(tinyxml2::XML_NO_ERROR != m_CacheDocument.LoadFile(m_CacheFilePath.c_str())) {
        auto pRoot = m_CacheDocument.NewElement("Cache");
        m_CacheDocument.InsertFirstChild(pRoot);
    }
    m_pCacheRoot = m_CacheDocument.RootElement();
}