// sktoolslib - common files for SK tools

// Copyright (C) 2013, 2017 - Stefan Kueng

// 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 2
// 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, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "stdafx.h"
#include "Language.h"
#include "codecvt.h"
#include "StringUtils.h"

#include <Commctrl.h>
#include <Shlwapi.h>
#include <fstream>
#include <vector>
#include <algorithm>
#include <cctype>
#include <memory>
#include <functional>
#include <wchar.h> //wcsrchr

#define MAX_STRING_LENGTH   (4*1024)


bool CLanguage::LoadFile( const std::wstring& path )
{
    langmap.clear();	//[JOJO]
    if (!PathFileExists(path.c_str()))
        return false;

    FILE *file = _wfsopen(path.c_str(), L"rt,ccs=UTF-8", _SH_DENYNO);	//[JOJO]
    if (file == NULL)
    {
        return false;
    }
    WCHAR line[MAX_STRING_LENGTH];
    std::vector<std::wstring> entry;
    do
    {
        ////////////////////////////////////////////////////////////
        // [JOJO]
        line[0] = '\0';
        fgetws(line, MAX_STRING_LENGTH, file);
        WCHAR *p; if ((p = wcsrchr(line, '\n')) != NULL) *p = '\0';
        ////////////////////////////////////////////////////////////
        if (line[0] == '\0')
        {
            //empty line means end of entry!
            std::wstring msgid;
            std::wstring msgstr;
            int type = 0;
            for (auto I = entry.begin(); I != entry.end(); ++I)
            {
                const WCHAR *str = I->c_str();
                if (wcsncmp(str, L"# ", 2)==0)
                {
                    //user comment
                    type = 0;
                }
                else if (wcsncmp(str, L"#.", 2)==0)
                {
                    //automatic comments
                    type = 0;
                }
                else if (wcsncmp(str, L"#,", 2)==0)
                {
                    //flag
                    type = 0;
                }
                else if (wcsncmp(str, L"msgid", 5)==0)
                {
                    //message id
                    msgid = str;
                    msgid = std::wstring(msgid.substr(7, msgid.size() - 8));

                    std::wstring s = msgid;
                    s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
                    type = 1;
                }
                else if (wcsncmp(str, L"msgstr", 6)==0)
                {
                    //message string
                    msgstr = str;
                    msgstr = msgstr.substr(8, msgstr.length() - 9);
                    type = 2;
                }
                else if (wcsncmp(str, L"\"", 1)==0)
                {
                    std::wstring temp = str;
                    if (type == 1)
                        msgid += temp.substr(1, temp.length()-2);
                    else if (type == 2)
                        msgstr += temp.substr(1, temp.length()-2);
                }
            }
            entry.clear();
            SearchReplace(msgid, L"\\\"", L"\"");
            SearchReplace(msgid, L"\\n", L"\n");
            SearchReplace(msgid, L"\\r", L"\r");
            SearchReplace(msgid, L"\\\\", L"\\");
            SearchReplace(msgstr, L"\\\"", L"\"");
            SearchReplace(msgstr, L"\\n", L"\n");
            SearchReplace(msgstr, L"\\r", L"\r");
            SearchReplace(msgstr, L"\\\\", L"\\");
            if (!msgid.empty() && !msgstr.empty())
                langmap[msgid] = msgstr;
            msgid.clear();
            msgstr.clear();
        }
        else
        {
            entry.push_back(line);
        }
    } while (!feof(file));
    fclose(file);

    return true;
}

std::wstring CLanguage::GetTranslatedString( const std::wstring& s )
{
    return GetTranslatedString(s, &langmap);
}

std::wstring CLanguage::GetTranslatedString( const std::wstring& s, std::map<std::wstring, std::wstring>* pLangMap )
{
    auto foundIt = pLangMap->find(s);
    if ((foundIt != pLangMap->end()) && (!foundIt->second.empty()))
        return foundIt->second;
    return s;
}

void CLanguage::TranslateWindow( HWND hWnd )
{
    // iterate over all windows and replace their
    // texts with the translation
    TranslateWindowProc(hWnd, (LPARAM)&langmap);
    EnumChildWindows(hWnd, TranslateWindowProc, (LPARAM)&langmap);
}

void CLanguage::TranslateMenu(HMENU hMenu)
{
    auto count = GetMenuItemCount(hMenu);
    for (int i = 0; i < count; ++i)
    {
        MENUITEMINFO mii = { 0 };
        mii.cbSize = sizeof(MENUITEMINFO);
        mii.fMask = MIIM_STRING | MIIM_SUBMENU;
        mii.dwTypeData = 0;
        if (GetMenuItemInfo(hMenu, i, MF_BYPOSITION, &mii))
        {
            if (mii.hSubMenu)
                TranslateMenu(mii.hSubMenu);
            if (mii.cch)
            {
                ++mii.cch;
                auto textbuf = std::make_unique<wchar_t[]>(mii.cch + 1);
                mii.dwTypeData = textbuf.get();
                if (GetMenuItemInfo(hMenu, i, MF_BYPOSITION, &mii))
                {
                    auto translated = GetTranslatedString(textbuf.get());
                    mii.fMask = MIIM_STRING;
                    mii.dwTypeData = const_cast<wchar_t*>(translated.c_str());
                    SetMenuItemInfo(hMenu, i, MF_BYPOSITION, &mii);
                }
            }
        }
    }
}

BOOL CALLBACK CLanguage::TranslateWindowProc( HWND hwnd, LPARAM lParam )
{
    ////////////////////////////////////////////////////////////
    // [JOJO]
    WCHAR classname[1024] = {0};
    GetClassName(hwnd, classname, _countof(classname));
    if (wcscmp(classname, L"Edit") == 0)
        return TRUE;
    ////////////////////////////////////////////////////////////

    std::map<std::wstring,std::wstring> * pLangMap = (std::map<std::wstring,std::wstring> *)lParam;
    int length = GetWindowTextLength(hwnd);
    auto text = std::make_unique<wchar_t[]>(length+1);
    std::wstring translatedString;
    if (GetWindowText(hwnd, text.get(), length+1))
    {
        translatedString = GetTranslatedString(text.get(), pLangMap);
        SetWindowText(hwnd, translatedString.c_str());
    }

    if (*classname)	//[JOJO]
    {
        if (wcscmp(classname, L"ComboBox")==0)
        {
            // translate the items in the combobox
            int nSel = (int)SendMessage(hwnd, CB_GETCURSEL, 0, 0);
            int nCount = (int)SendMessage(hwnd, CB_GETCOUNT, 0, 0);
            for (int i = 0; i < nCount; ++i)
            {
                length = (int)SendMessage(hwnd, CB_GETLBTEXTLEN, i, 0);
                auto buf = std::make_unique<wchar_t[]>(length+1);
                SendMessage(hwnd, CB_GETLBTEXT, i, (LPARAM)buf.get());
                std::wstring sTranslated = GetTranslatedString(buf.get(), pLangMap);
                SendMessage(hwnd, CB_INSERTSTRING, i, (LPARAM)sTranslated.c_str());
                SendMessage(hwnd, CB_DELETESTRING, i+1, 0);
            }
            SendMessage(hwnd, CB_SETCURSEL, nSel, 0);
        }
        else if (wcscmp(classname, L"Button")==0)
        {
            LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE);
            if (((style & BS_GROUPBOX)==0) &&
                ((style & BS_CHECKBOX) || (style & BS_AUTORADIOBUTTON) || (style & BS_RADIOBUTTON)))
            {
                // adjust the width of checkbox and radio buttons
                HDC hDC = GetWindowDC(hwnd);
                RECT controlrect;
                RECT controlrectorig;
                GetWindowRect(hwnd, &controlrect);
                ::MapWindowPoints(nullptr, GetParent(hwnd), (LPPOINT)&controlrect, 2);
                controlrectorig = controlrect;
                if (hDC)
                {
                    HFONT hFont = GetWindowFont(hwnd);
                    HGDIOBJ hOldFont = ::SelectObject(hDC, hFont);
                    if (DrawText(hDC, translatedString.c_str(), -1, &controlrect, DT_WORDBREAK | DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_CALCRECT))
                    {
                        // now we have the rectangle the control really needs
                        if ((controlrectorig.right - controlrectorig.left) > (controlrect.right - controlrect.left))
                        {
                            // we're dealing with radio buttons and check boxes,
                            // which means we have to add a little space for the checkbox
                            // the value of 3 pixels added here is necessary in case certain visual styles have
                            // been disabled. Without this, the width is calculated too short.
                            const int checkWidth = GetSystemMetrics(SM_CXMENUCHECK) + 2*GetSystemMetrics(SM_CXEDGE) + 3;
                            controlrectorig.right = controlrectorig.left + (controlrect.right - controlrect.left) + checkWidth;
                            MoveWindow(hwnd, controlrectorig.left, controlrectorig.top, controlrectorig.right-controlrectorig.left, controlrectorig.bottom-controlrectorig.top, TRUE);
                        }
                    }
                    SelectObject(hDC, hOldFont);
                    ReleaseDC(hwnd, hDC);
                }
            }
        }
        else if (wcscmp(classname, L"SysHeader32")==0)
        {
            // translate column headers in list and other controls
            int nCount = Header_GetItemCount(hwnd);
            auto buf = std::make_unique<wchar_t[]>(270);
            for (int i = 0; i < nCount; ++i)
            {
                HDITEM hdi = {0};
                hdi.mask = HDI_TEXT;
                hdi.pszText = buf.get();
                hdi.cchTextMax = 270;
                Header_GetItem(hwnd, i, &hdi);
                std::wstring sTranslated = GetTranslatedString(buf.get(), pLangMap);
                hdi.pszText = const_cast<LPWSTR>(sTranslated.c_str());
                Header_SetItem(hwnd, i, &hdi);
            }
        }
    }

    return TRUE;
}

CLanguage& CLanguage::Instance()
{
    static CLanguage instance;
    return instance;
}
