/*
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include <memory>
#include "WinApp.h"
#include "Log.h"
#include "SysInfo.h"
#include "FileUtils.h"
#include "ErrorHandling.h"


// MessageBox - Requires linking with user32


namespace {

class LastErrorGuiLogAppender : public LogAppender {
public:
    virtual void append(const LogEvent& v) {
        JP_TRY;

        const std::wstring msg = (tstrings::any()
                << app::lastErrorMsg()).wstr();
        MessageBox(0, msg.c_str(),
            FileUtils::basename(SysInfo::getProcessModulePath()).c_str(),
            MB_ICONERROR | MB_OK);

        JP_CATCH_ALL;
    }
};


class Console {
public:
    Console() {
        if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
            // Failed to connect to parent's console. Create our own.
            if (!AllocConsole()) {
                // We already have a console, no need to redirect std I/O.
                return;
            }
        }

        stdoutChannel = std::unique_ptr<Channel>(new Channel(stdout));
        stderrChannel = std::unique_ptr<Channel>(new Channel(stderr));
    }

    struct FileCloser {
        typedef FILE* pointer;

        void operator()(pointer h) {
            ::fclose(h);
        }
    };

    typedef std::unique_ptr<
        FileCloser::pointer,
        FileCloser
    > UniqueFILEHandle;

private:
    class Channel {
    public:
        Channel(FILE* stdFILEHandle): stdFILEHandle(stdFILEHandle) {
            const char* stdFileName = "CONOUT$";
            const char* openMode = "w";
            if (stdFILEHandle == stdin) {
                stdFileName = "CONIN$";
                openMode = "r";
            }

            FILE* fp = 0;
            freopen_s(&fp, stdFileName, openMode, stdFILEHandle);

            fileHandle = UniqueFILEHandle(fp);

            std::ios_base::sync_with_stdio();
        }

        virtual ~Channel() {
            JP_TRY;

            FILE* fp = 0;
            fileHandle = UniqueFILEHandle(fp);
            std::ios_base::sync_with_stdio();

            JP_CATCH_ALL;
        }

    private:
        UniqueFILEHandle fileHandle;
        FILE *stdFILEHandle;
    };

    std::unique_ptr<Channel> stdoutChannel;
    std::unique_ptr<Channel> stderrChannel;
};

} // namespace


namespace app {
int wlaunch(const std::nothrow_t&, LauncherFunc func) {
    std::unique_ptr<Console> console;
    JP_TRY;
    if (app::isWithLogging()) {
        console = std::unique_ptr<Console>(new Console());
    }
    JP_CATCH_ALL;

    LastErrorGuiLogAppender lastErrorLogAppender;
    TeeLogAppender logAppender(&app::defaultLastErrorLogAppender(),
            &lastErrorLogAppender);
    return app::launch(std::nothrow, func, &logAppender);
}
} // namespace app
