/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2020 Ian McInerney <Ian.S.McInerney at ieee.org>
 * Copyright (C) 2020-2022 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * 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 <kiplatform/environment.h>
#include <wx/intl.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/string.h>
#include <wx/tokenzr.h>
#include <wx/app.h>

#include <Windows.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <winhttp.h>

#include <Softpub.h>
#include <wincrypt.h>
#include <wintrust.h>


void KIPLATFORM::ENV::Init()
{
    // No tasks for this platform
}


bool KIPLATFORM::ENV::MoveToTrash( const wxString& aPath, wxString& aError )
{
    // The filename field must be a double-null terminated string
    wxString temp = aPath + '\0';

    SHFILEOPSTRUCT fileOp;
    ::ZeroMemory( &fileOp, sizeof( fileOp ) );

    fileOp.hwnd   = nullptr; // Set to null since there is no progress dialog
    fileOp.wFunc  = FO_DELETE;
    fileOp.pFrom  = temp.c_str();
    fileOp.pTo    = nullptr; // Set to to NULL since we aren't moving the file
    fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOERRORUI | FOF_NOCONFIRMATION | FOF_SILENT;

    int eVal = SHFileOperation( &fileOp );

    if( eVal != 0 )
    {
        aError = wxString::Format( _( "Error code: %d" ), eVal );
        return false;
    }

    return true;
}


bool KIPLATFORM::ENV::IsNetworkPath( const wxString& aPath )
{
    return ::PathIsNetworkPathW( aPath.wc_str() );
}


wxString KIPLATFORM::ENV::GetDocumentsPath()
{
    // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
    // complains about not existing app. so use a dummy app
    if( wxTheApp ==  nullptr )
    {
        wxApp dummy;
        return wxStandardPaths::Get().GetDocumentsDir();
    }

    return wxStandardPaths::Get().GetDocumentsDir();
}


wxString KIPLATFORM::ENV::GetUserConfigPath()
{
    // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
    // complains about not existing app. so use a dummy app
    if( wxTheApp ==  nullptr )
    {
        wxApp dummy;
        return wxStandardPaths::Get().GetUserConfigDir();
    }

    return wxStandardPaths::Get().GetUserConfigDir();
}


wxString KIPLATFORM::ENV::GetUserDataPath()
{
    // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
    // complains about not existing app. so use a dummy app
    if( wxTheApp ==  nullptr )
    {
        wxApp dummy;
        return wxStandardPaths::Get().GetUserDataDir();
    }

    return wxStandardPaths::Get().GetUserDataDir();
}


wxString KIPLATFORM::ENV::GetUserLocalDataPath()
{
    // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
    // complains about not existing app. so use a dummy app
    if( wxTheApp == nullptr )
    {
        wxApp dummy;
        return wxStandardPaths::Get().GetUserLocalDataDir();
    }

    return wxStandardPaths::Get().GetUserLocalDataDir();
}


wxString KIPLATFORM::ENV::GetUserCachePath()
{
    // Unfortunately AppData/Local is the closest analog to "Cache" directories of other platforms

    // Make sure we don't include the "appinfo" (appended app name)

    // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
    // complains about not existing app. so use a dummy app
    if( wxTheApp ==  nullptr )
    {
        wxApp dummy;
        wxStandardPaths::Get().UseAppInfo( wxStandardPaths::AppInfo_None );

        return wxStandardPaths::Get().GetUserLocalDataDir();
   }

    wxStandardPaths::Get().UseAppInfo( wxStandardPaths::AppInfo_None );

    return wxStandardPaths::Get().GetUserLocalDataDir();
}


bool KIPLATFORM::ENV::GetSystemProxyConfig( const wxString& aURL, PROXY_CONFIG& aCfg )
{
    // Original source from Microsoft sample (public domain)
    // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/WinhttpProxy/cpp/GetProxy.cpp#L844
    bool                                 autoProxyDetect = false;
    WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig = { 0 };
    WINHTTP_AUTOPROXY_OPTIONS            autoProxyOptions = { 0 };
    WINHTTP_PROXY_INFO                   autoProxyInfo = { 0 };
    HINTERNET                            proxyResolveSession = NULL;
    bool                                 success = false;

    if( WinHttpGetIEProxyConfigForCurrentUser( &ieProxyConfig ) )
    {
        // welcome to the wonderful world of IE
        // we use the ie config simply to handle it off to the other win32 api
        if( ieProxyConfig.fAutoDetect )
        {
            autoProxyDetect = true;
        }

        if( ieProxyConfig.lpszAutoConfigUrl != NULL )
        {
            autoProxyDetect = true;
            autoProxyOptions.lpszAutoConfigUrl = ieProxyConfig.lpszAutoConfigUrl;
        }
    }
    else if( GetLastError() == ERROR_FILE_NOT_FOUND )
    {
        // this is the only error code where we want to continue attempting to find a proxy
        autoProxyDetect = true;
    }

    if( autoProxyDetect )
    {
        proxyResolveSession =
                WinHttpOpen( NULL, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME,
                             WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC );

        if( proxyResolveSession )
        {
            // either we use the ie url or we set the auto detect mode
            if( autoProxyOptions.lpszAutoConfigUrl != NULL )
            {
                autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
            }
            else
            {
                autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
                autoProxyOptions.dwAutoDetectFlags =
                        WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
            }

            // dont do auto logon at first, this allows windows to use an cache
            // per https://docs.microsoft.com/en-us/windows/win32/winhttp/autoproxy-cache
            autoProxyOptions.fAutoLogonIfChallenged = FALSE;

            autoProxyDetect = WinHttpGetProxyForUrl( proxyResolveSession, aURL.c_str(),
                                                     &autoProxyOptions, &autoProxyInfo );

            if( !autoProxyDetect && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE )
            {
                autoProxyOptions.fAutoLogonIfChallenged = TRUE;

                // try again with auto login now
                autoProxyDetect = WinHttpGetProxyForUrl( proxyResolveSession, aURL.c_str(),
                                                         &autoProxyOptions, &autoProxyInfo );
            }

            WinHttpCloseHandle( proxyResolveSession );
        }

    }

    if( autoProxyDetect )
    {
        if( autoProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY )
        {
            // autoProxyInfo will return a list of proxies that are semicolon delimited
            // todo...maybe figure out better selection logic
            wxString          proxyList = autoProxyInfo.lpszProxy;
            wxStringTokenizer tokenizer( proxyList, wxT( ";" ) );

            if( tokenizer.HasMoreTokens() )
            {
                aCfg.host = tokenizer.GetNextToken();
            }

            success = true;
        }
    }
    else
    {
        if( ieProxyConfig.lpszProxy != NULL )
        {
            // ie proxy configs may return : or :: for an empty proxy

            aCfg.host = ieProxyConfig.lpszProxy;

            if(aCfg.host != ":" && aCfg.host != "::")
            {
                success = true;
            }
        }
    }

    // We have to clean up the strings the win32 api returned
    if( autoProxyInfo.lpszProxy )
    {
        GlobalFree( autoProxyInfo.lpszProxy );
        autoProxyInfo.lpszProxy = NULL;
    }

    if( autoProxyInfo.lpszProxyBypass )
    {
        GlobalFree( autoProxyInfo.lpszProxyBypass );
        autoProxyInfo.lpszProxyBypass = NULL;
    }

    if( ieProxyConfig.lpszAutoConfigUrl != NULL )
    {
        GlobalFree( ieProxyConfig.lpszAutoConfigUrl );
        ieProxyConfig.lpszAutoConfigUrl = NULL;
    }

    if( ieProxyConfig.lpszProxy != NULL )
    {
        GlobalFree( ieProxyConfig.lpszProxy );
        ieProxyConfig.lpszProxy = NULL;
    }

    if( ieProxyConfig.lpszProxyBypass != NULL )
    {
        GlobalFree( ieProxyConfig.lpszProxyBypass );
        ieProxyConfig.lpszProxyBypass = NULL;
    }

    return success;
}


bool KIPLATFORM::ENV::VerifyFileSignature( const wxString& aPath )
{
    WINTRUST_FILE_INFO fileData;
    memset( &fileData, 0, sizeof( fileData ) );
    fileData.cbStruct = sizeof( WINTRUST_FILE_INFO );
    fileData.pcwszFilePath = aPath.wc_str();

    // verifies entire certificate chain
    GUID policy = WINTRUST_ACTION_GENERIC_VERIFY_V2;

    WINTRUST_DATA trustData;
    memset( &trustData, 0, sizeof( trustData ) );

    trustData.cbStruct = sizeof( trustData );
    trustData.dwUIChoice = WTD_UI_NONE;
    // revocation checking incurs latency penalities due to need for online queries
    trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
    trustData.dwUnionChoice = WTD_CHOICE_FILE;
    trustData.dwStateAction = WTD_STATEACTION_VERIFY;
    trustData.pFile = &fileData;


    bool verified = false;
    LONG status = WinVerifyTrust( NULL, &policy, &trustData );

    verified = ( status == ERROR_SUCCESS );

    // Cleanup/release (yes its weird looking)
    trustData.dwStateAction = WTD_STATEACTION_CLOSE;
    WinVerifyTrust( NULL, &policy, &trustData );

    return verified;
}