#include "stdafx.h"
#include "MAC.h"
#include "FormatPlugin.h"
#include "Markup.h"
#include "MACFile.h"
#include "FormatPluginConfigureDlg.h"
#include "FormatPluginInfoDlg.h"
#include "MACDlg.h"

using namespace APE;

CFormatPlugin::CFormatPlugin(CMACDlg * pMACDlg, int nIndex)
{
    // initialize
    m_pMACDlg = pMACDlg;
    m_nIndex = nIndex;
    m_bIsValid = false;
    m_bHasConfiguration = false;
}

CFormatPlugin::~CFormatPlugin()
{
}

bool CFormatPlugin::Load(const CString & strAPXFilename)
{
    bool bRetVal = false;

    m_strAPXFilename = strAPXFilename;

    CString strAPX;
    if (ReadWholeFile(m_strAPXFilename, strAPX))
    {
        // parse the XML
        CMarkup XML(strAPX);
        if (XML.FindElem(_T("APX")) && (XML.GetAttrib(_T("version")) == _T("1.0")))
        {
            // general info
            if (XML.FindChildElem(_T("General")))
            {
                XML.IntoElem();
                m_strName = XML.GetChildData(_T("Name"));
                m_strURL = XML.GetChildData(_T("URL"));
                m_strAuthor = XML.GetChildData(_T("Author"));
                m_strVersion = XML.GetChildData(_T("Version"));
                m_strDescription = XML.GetChildData(_T("Description"));
                XML.OutOfElem();
            }

            // mode info
            ParseModeInfo(XML, MODE_COMPRESS, _T("Compress"));
            ParseModeInfo(XML, MODE_DECOMPRESS, _T("Decompress"));
            ParseModeInfo(XML, MODE_VERIFY, _T("Verify"));
            ParseModeInfo(XML, MODE_CHECK, _T("Check"));

            // configure
            m_bHasConfiguration = false;
            if (XML.FindChildElem(_T("Configure")))
            {
                m_bHasConfiguration = true;

                XML.IntoElem();

                for (int z = 0; z < 3; z++)
                {
                    CString strName; strName.Format(_T("Configure%d"), z + 1);

                    if (XML.FindChildElem(strName))
                    {
                        XML.IntoElem();
                        m_strConfigureDescription[z] = XML.GetChildData(_T("Description"));

                        CString strSetting;
                        strSetting.Format(_T("Format Plugin - %s - Configure %d"), GetName().GetString(), z + 1);
                        m_strConfigureValue[z] = theApp.GetSettings()->LoadSetting(strSetting, XML.GetChildData(_T("DefaultValue")));

                        XML.OutOfElem();
                    }
                }

                XML.OutOfElem();
            }

            bRetVal = true;
        }
    }

    // check
    {
        for (int z = 0; z < m_aryModeInfo[MODE_CHECK].GetSize(); z++)
        {
            CFormatPluginLevelInfo & Level = m_aryModeInfo[MODE_CHECK].GetAt(z);
            if (Level.m_strApplication.GetLength() > 0)
            {
                MAC_FILE File;
                File.m_Mode = MODE_CHECK;
                if (Process(&File) != ERROR_SUCCESS)
                {
                    bRetVal = false;
                }
            }
        }
    }

    if (bRetVal)
        m_bIsValid = true;

    return bRetVal;
}

void CFormatPlugin::ParseModeInfo(CMarkup & XML, APE_MODES Mode, const CString & strKeyword)
{
    m_aryModeInfo[Mode].RemoveAll();

    if (XML.FindChildElem(strKeyword))
    {
        XML.IntoElem();

        while (XML.FindChildElem())
        {
            XML.IntoElem();
            CFormatPluginLevelInfo Level(XML);
            m_aryModeInfo[Mode].Add(Level);
            XML.OutOfElem();
        }

        XML.OutOfElem();
    }
}

bool CFormatPlugin::BuildMenu(CMenu * pMenu, int nBaseID)
{
    if (!GetValid())
        return false;

    for (int z = 0; z < m_aryModeInfo[MODE_COMPRESS].GetSize(); z++)
    {
        bool bChecked = (theApp.GetSettings()->GetFormat() == m_strName) &&
            (theApp.GetSettings()->GetLevel() == z);

        UINT_PTR nNewID = static_cast<UINT_PTR>(nBaseID) + static_cast<UINT_PTR>(z)  + static_cast<UINT_PTR>(10);
        pMenu->AppendMenu(MF_STRING | (bChecked ? MF_CHECKED : MF_UNCHECKED), nNewID, m_aryModeInfo[MODE_COMPRESS][z].m_strName);
    }

    pMenu->AppendMenu(MF_SEPARATOR);

    if (m_bHasConfiguration)
        pMenu->AppendMenu(MF_STRING, nBaseID + 0, _T("Configure..."));

    pMenu->AppendMenu(MF_STRING, UINT_PTR(nBaseID) + 1, _T("Info..."));

    return (pMenu->GetSafeHmenu() != APE_NULL) ? true : false;
}

bool CFormatPlugin::ProcessMenuCommand(int nCommand)
{
    if (nCommand == 0)
    {
        CFormatPluginConfigureDlg dlgConfigure(m_pMACDlg, m_strConfigureDescription[0], m_strConfigureValue[0],
            m_strConfigureDescription[1], m_strConfigureValue[1], m_strConfigureDescription[2], m_strConfigureValue[2]);

        if (dlgConfigure.DoModal() == IDOK)
        {
            CString strSetting;

            m_strConfigureValue[0] = dlgConfigure.m_strConfigureEdit1;
            strSetting.Format(_T("Format Plugin - %s - Configure 1"), GetName().GetString());
            theApp.GetSettings()->SaveSetting(strSetting, m_strConfigureValue[0]);

            m_strConfigureValue[1] = dlgConfigure.m_strConfigureEdit2;
            strSetting.Format(_T("Format Plugin - %s - Configure 2"), GetName().GetString());
            theApp.GetSettings()->SaveSetting(strSetting, m_strConfigureValue[1]);

            m_strConfigureValue[2] = dlgConfigure.m_strConfigureEdit3;
            strSetting.Format(_T("Format Plugin - %s - Configure 3"), GetName().GetString());
            theApp.GetSettings()->SaveSetting(strSetting, m_strConfigureValue[2]);
        }
    }
    else if (nCommand == 1)
    {
        CFormatPluginInfoDlg dlgPluginInfo(m_pMACDlg, m_strName, m_strVersion, m_strAuthor, m_strDescription, m_strURL);
        dlgPluginInfo.DoModal();
    }
    else if ((nCommand >= 10) && (nCommand < 100))
    {
        int nMode = nCommand - 10;
        theApp.GetSettings()->SetCompression(GetName(), nMode);
    }

    return true;
}

CString CFormatPlugin::GetOutputExtension(APE_MODES Mode, const CString & strInputFilename, int nLevel)
{
    CString strExtension;

    CFormatPluginLevelInfo * pLevelInfo = GetLevelInfo(Mode, strInputFilename, nLevel);
    if (pLevelInfo != APE_NULL)
    {
        strExtension = pLevelInfo->m_strOutputExtension;
    }

    // custom code for WavPack
    if ((m_strName == _T("WavPack")) && (pLevelInfo != APE_NULL) && ((Mode == MODE_DECOMPRESS) || (Mode == MODE_CONVERT)))
    {
        CString strPath = GetProgramPath() + _T("External\\");
        CString strApplication = strPath + pLevelInfo->m_strApplication;

        CString strExtensionCheck = _T("-ssq \"") + strInputFilename + _T("\"");
        int nExitCode = 0;
        CString strOutput;
        bool bSuccess = ExecuteProgramBlocking(strApplication, strExtensionCheck, &nExitCode, false, &strOutput);

        if (bSuccess)
        {
            int nExtensionEnd = strOutput.Find(_T("' extension"));
            strOutput = strOutput.Left(nExtensionEnd);
            int nExtensionStart = strOutput.ReverseFind('\'');
            if ((strOutput.IsEmpty() == false) && (nExtensionStart >= 0))
            {
                strExtension = strOutput.Mid(nExtensionStart + 1, 1000);
                strExtension = _T(".") + strExtension;
            }
        }
    }

    return strExtension;
}

int CFormatPlugin::Process(MAC_FILE * pInfo)
{
    int nRetVal = ERROR_UNDEFINED;

    CFormatPluginLevelInfo * pLevelInfo = GetLevelInfo(pInfo->m_Mode, pInfo->m_strInputFilename, pInfo->m_nLevel);
    if (pLevelInfo != APE_NULL)
    {
        CString strCommandLine = pLevelInfo->m_strCommandLine;
        strCommandLine.Replace(_T("[INPUT]"), _T("\"") + pInfo->m_strInputFilename + _T("\""));
        strCommandLine.Replace(_T("[OUTPUT]"), _T("\"") + pInfo->m_strWorkingFilename + _T("\""));
        strCommandLine.Replace(_T("[CONFIGURE 1]"), m_strConfigureValue[0]);
        strCommandLine.Replace(_T("[CONFIGURE 2]"), m_strConfigureValue[1]);
        strCommandLine.Replace(_T("[CONFIGURE 3]"), m_strConfigureValue[2]);

        // try to create the working file to check for UAC permission problems
        if (pInfo->m_strWorkingFilename.IsEmpty() == false)
        {
            HANDLE hFile = CreateFile(pInfo->m_strWorkingFilename, GENERIC_WRITE, 0, APE_NULL, CREATE_ALWAYS, 0, APE_NULL);
            if (hFile == INVALID_HANDLE_VALUE)
            {
                DWORD dwError = GetLastError();
                if (dwError == ERROR_ACCESS_DENIED)
                    return ERROR_UAC_PERMISSION;
                else
                    return ERROR_UNDEFINED;
            }
            CloseHandle(hFile);
            DeleteFile(pInfo->m_strWorkingFilename);
        }

        // set everything up
        pInfo->m_nStageProgress = 0;

        CString strPath = GetProgramPath() + _T("External\\");
        CString strApplication = strPath + pLevelInfo->m_strApplication;

        int nExitCode = -1;

        // run program
        bool bSuccess = ExecuteProgramBlocking(strApplication, strCommandLine, &nExitCode, false);

        // process result
        if (pLevelInfo->m_strSuccessReturn.IsEmpty() == false)
        {
            if (bSuccess && (nExitCode == _ttoi(pLevelInfo->m_strSuccessReturn)))
                nRetVal = ERROR_SUCCESS;
            else
                nRetVal = ERROR_UNDEFINED;
        }
        else
        {
            if (pLevelInfo->m_strCommandLine.Find(_T("[OUTPUT]")) != -1)
                nRetVal = (bSuccess && FileExists(pInfo->m_strWorkingFilename)) ? ERROR_SUCCESS : ERROR_UNDEFINED;
            else
                nRetVal = ERROR_SUCCESS;
        }
    }

    return nRetVal;
}

CString CFormatPlugin::GetInputExtensions(APE_MODES Mode)
{
    CStringArrayEx aryExtensions;
    for (int z = 0; z < m_aryModeInfo[Mode].GetSize(); z++)
        aryExtensions.Append(m_aryModeInfo[Mode][z].m_aryInputExtensions);

    if (Mode == MODE_CONVERT)
    {
        for (int z = 0; z < m_aryModeInfo[MODE_DECOMPRESS].GetSize(); z++)
            aryExtensions.Append(m_aryModeInfo[MODE_DECOMPRESS][z].m_aryInputExtensions);
    }

    aryExtensions.RemoveDuplicates(false);
    return aryExtensions.GetList(_T(";"));
}

CFormatPluginLevelInfo * CFormatPlugin::GetLevelInfo(APE_MODES Mode, const CString & strInputFilename, int nLevel)
{
    // start with APE_NULL
    CFormatPluginLevelInfo * pLevelInfo = APE_NULL;

    // for decompress and verify, we pick the level info based on the input filename
    if (Mode == MODE_DECOMPRESS || Mode == MODE_VERIFY || Mode == MODE_CONVERT)
    {
        nLevel = -1;
        CString strExtension = CFilename(strInputFilename).GetExtension();
        for (int z = 0; z < m_aryModeInfo[Mode].GetSize(); z++)
        {
            if (m_aryModeInfo[Mode][z].m_aryInputExtensions.Find(strExtension) != -1)
            {
                nLevel = z;
                break;
            }
        }
    }

    // for check, we just pick the first
    if (Mode == MODE_CHECK)
    {
        nLevel = 0;
    }

    // if we found a suitable level, get it
    if ((nLevel >= 0) && (nLevel < m_aryModeInfo[Mode].GetSize()))
    {
        pLevelInfo = &m_aryModeInfo[Mode][nLevel];
    }

    // return
    return pLevelInfo;
}
