#include "priv.h"
#include "zaxxon.h"
#include "guids.h"
#include "shlwapip.h"
#include "resource.h"
#include "commdlg.h"
#include "varutil.h"

#define ID_TOOLBAR      100
#define ID_LISTVIEW     101

#define EDIT_NEW         0
#define EDIT_ADD         1
#define EDIT_REMOVE      2
#define EDIT_LOAD        3
#define EDIT_SAVE        4
#define EDIT_SORT        5


CSong::CSong():_cRef(1)
{

}

void CSong::AddRef()
{
    InterlockedIncrement((LONG*)&_cRef);
}

void CSong::Release()
{
    InterlockedDecrement((LONG*)&_cRef);
    if (_cRef == 0)
        delete this;
}


CZaxxonEditor::CZaxxonEditor(CZaxxon* pz):_pzax(pz)
{
    WNDCLASS wc = {0};
    wc.lpszClassName = TEXT("ZaxxonEditor");
    wc.lpfnWndProc = CZaxxonEditor::s_WndProc;
    wc.hInstance = HINST_THISDLL;
    wc.hbrBackground = HBRUSH(COLOR_ACTIVECAPTION + 1);
    RegisterClass(&wc);

    hSongList = DPA_Create(10);
}

int CALLBACK SongDestroyCallback(void* p, void*)
{
    CSong* psong = (CSong*)p;
    psong->Release();
    return 1;
}

CZaxxonEditor::~CZaxxonEditor()
{
    if (_hwnd)
    {
        DestroyWindow(_hwnd);
        _hwnd = NULL;
    }

    DPA_DestroyCallback(hSongList, SongDestroyCallback, NULL);
}


int ReadLineFromStream(IStream* pstm, LPTSTR pszLine, DWORD cch)
{
    // Assume this is an ANSI string
    char szLine[MAX_PATH];
    DWORD dwRead;
    int iLine = 0;
    ULARGE_INTEGER UliStartingFrom;
    LARGE_INTEGER  liSeek = {0};

    pstm->Seek(liSeek, STREAM_SEEK_CUR, &UliStartingFrom);
    pstm->Read(szLine, ARRAYSIZE(szLine), &dwRead);
    if (dwRead > 0)
    {
        // Now convert to String
        SHAnsiToUnicode(szLine, pszLine, dwRead);

        pszLine[MAX_PATH - 1] = '\0'; //Null terminate the string

        // Get the number of characters in the line
        LPTSTR pszNewLine = StrStr(pszLine, TEXT("\r"));

        if (pszNewLine)
        {
            *pszNewLine++ = TEXT('\0'); // Nuke \r
            *pszNewLine = TEXT('\0');   // Nuke \n
        }
        else
        {
            pszNewLine = StrStr(pszLine, TEXT("\n"));
            if (pszNewLine)
                *pszNewLine = TEXT('\0');   // Nuke \n
        }



        iLine = lstrlen(pszLine);

        // Now take that, and set the seek position to 2 more than that (One for the \r and \n
        liSeek.QuadPart = UliStartingFrom.QuadPart + iLine + 1 + 1;
        pstm->Seek(liSeek, STREAM_SEEK_SET, NULL);
    }

    return iLine;
}


void CZaxxonEditor::UpdateSong(CSong* psong)
{

    int i = ListView_MapIDToIndex(_hwndList, psong->_id);
    if (i >= 0)
    {
        TCHAR szTitle[MAX_PATH];
        wsprintf(szTitle, TEXT("%s - %s"), psong->szArtist, psong->szTitle);


        LVITEM lv;
        lv.mask = LVIF_TEXT;
        lv.iItem = i;
        lv.iSubItem = 0;
        lv.pszText = szTitle;
        ListView_SetItem(_hwndList,&lv);

        lv.iSubItem = 1;
        lv.pszText = psong->szDuration;
        ListView_SetItem(_hwndList,&lv);
    }
}

void CZaxxonEditor::LoadPlaylist()
{
    TCHAR sz[MAX_PATH];
    TCHAR szInit[MAX_PATH];
    OPENFILENAME of = {0};
    of.lStructSize = sizeof(of);
    of.hwndOwner = _hwnd;
    of.lpstrFilter = TEXT("Playlist\0*.m3u\0\0");
    of.lpstrFile = sz;
    of.nMaxFile = MAX_PATH;
    of.lpstrDefExt = TEXT(".m3u");
    if (FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, szInit)))
        SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, szInit);

    of.lpstrInitialDir = szInit;
    of.Flags = OFN_ENABLESIZING | OFN_EXPLORER | OFN_FILEMUSTEXIST;

    if (GetOpenFileName(&of))
    {
        ClearPlaylist();
        IStream* pstm;
        if (SUCCEEDED(SHCreateStreamOnFile(sz, STGM_READ, &pstm)))
        {
            do
            {
                TCHAR szFilename[MAX_PATH];
                if (ReadLineFromStream(pstm, szFilename, ARRAYSIZE(szFilename)) > 0)
                {
                    AddFilename(szFilename);
                }
                else
                {
                    break;
                }
            }
            while (1);

            pstm->Release();
        }
    }
}

void CZaxxonEditor::SavePlaylist()
{
    TCHAR sz[MAX_PATH];
    TCHAR szInit[MAX_PATH];
    OPENFILENAME of = {0};
    of.lStructSize = sizeof(of);
    of.hwndOwner = _hwnd;
    of.lpstrFilter = TEXT("Playlist\0*.m3u\0\0");
    of.lpstrFile = sz;
    of.nMaxFile = MAX_PATH;
    of.lpstrDefExt = TEXT(".m3u");
    if (FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, szInit)))
        SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, szInit);

    of.lpstrInitialDir = szInit;
    of.Flags = OFN_ENABLESIZING | OFN_EXPLORER;

    if (GetSaveFileName(&of))
    {
        IStream* pstm;
        if (SUCCEEDED(SHCreateStreamOnFile(sz, STGM_CREATE | STGM_WRITE, &pstm)))
        {
            char szLine[MAX_PATH];

            int cCount = DPA_GetPtrCount(hSongList);
            for (int i=0; i < cCount; i++)
            {
                CSong* psong = (CSong*)DPA_FastGetPtr(hSongList, i);
                SHUnicodeToAnsi(psong->szSong, szLine, MAX_PATH);
                pstm->Write(szLine, lstrlenA(szLine), NULL);
                pstm->Write("\r\n", 2, NULL);
            }
            pstm->Write("\r\n", 2, NULL);
            pstm->Release();
        }
    }
}

void CZaxxonEditor::RemoveFromPlaylist()
{
    int iItem = ListView_GetNextItem(_hwndList, -1, MAKELPARAM(LVNI_SELECTED, 0));
    if (iItem >= 0 && iItem < DPA_GetPtrCount(hSongList))
    {
        _pzax->_pzax->RemoveSong(iItem);
        ListView_DeleteItem(_hwndList, iItem);
        CSong* psong = (CSong*)DPA_DeletePtr(hSongList, iItem);
        psong->Release();
    }

}

void CZaxxonEditor::ClearPlaylist()
{
    _pzax->_pzax->ClearPlaylist();

    DPA_DestroyCallback(hSongList, SongDestroyCallback, NULL);
    hSongList = DPA_Create(10);
    ListView_DeleteAllItems(_hwndList);
}


void CZaxxonEditor::InsertFilename(int i, PTSTR psz)
{
    CSong* pzs = new CSong;
    if (pzs)
    {
        StrCpy(pzs->szSong, psz);

        int iIndex = DPA_InsertPtr(hSongList, i, pzs);
        if (iIndex != -1)
        {
            LVITEM lv;
            lv.mask = LVIF_TEXT;
            lv.iItem = iIndex;
            lv.iSubItem = 0;
            lv.pszText = PathFindFileName(psz);
            ListView_InsertItem(_hwndList, &lv);

            pzs->_id = ListView_MapIndexToID(_hwndList, iIndex);
        
        
            _pzax->_pzax->AddSong(psz);
            CSongExtractionTask* pset = new CSongExtractionTask(_hwnd, pzs);
            if (pset)
            {
                if (_pzax->_pScheduler)
                    _pzax->_pScheduler->AddTask(pset, CLSID_Zaxxon, 0, ITSAT_MIN_PRIORITY);
                pset->Release();
            }
        }
    }
}

void CZaxxonEditor::AddFilename(PTSTR psz)
{
    InsertFilename(DSA_APPEND, psz);
}

void CZaxxonEditor::AddToPlaylist()
{
    OPENFILENAME of = {0};
    PTSTR psz = (PTSTR)LocalAlloc(LPTR, 4096);
    if (psz)
    {
        of.lStructSize = sizeof(of);
        of.hwndOwner = _hwnd;
        of.lpstrFilter = TEXT("Music\0*.mp3;*.wma\0\0");
        of.lpstrFile = psz;
        of.nMaxFile = 4096;
        of.Flags = OFN_ALLOWMULTISELECT | OFN_ENABLESIZING | OFN_EXPLORER | OFN_FILEMUSTEXIST;

        if (GetOpenFileName(&of))
        {
            TCHAR szName[MAX_PATH];
            PTSTR pszFilename = psz;
            int iLen = lstrlen(pszFilename);
            do
            {
                pszFilename += iLen + 1;    // Skip the last filename
                iLen = lstrlen(pszFilename);

                if (iLen > 0)
                {
                    StrCpy(szName, psz);        // Copy the path;
                    PathAppend(szName, pszFilename);  // Append the new name
                    AddFilename(szName);        // Append to list.
                }
            }
            while (iLen != 0);
            
        }

        LocalFree(psz);
    }
}

int FindSong(void* p1, void* p2, LPARAM lParam)
{
    PTSTR pszSong = (PTSTR)p1;
    CSong* psong = (CSong*)p2;
    return StrCmpI(pszSong, psong->szSong);
}

void CZaxxonEditor::HighlightSong(PTSTR psz)
{
    
    int iItem = DPA_Search(hSongList, psz, 0, FindSong, 0, NULL);
    if (iItem >= 0)
    {
        ListView_SetItemState(_hwndList, iItem, LVIS_SELECTED, LVIS_SELECTED);
    }
}


static const TCHAR* g_EditStrings    =   TEXT("New\0Add\0Delete\0Load\0Save\0Sort\0\0");

static const TBBUTTON g_crgEditButtons[] =
{
    {I_IMAGENONE,  EDIT_NEW,  TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 0},
    {I_IMAGENONE,  EDIT_ADD,  TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 1},
    {I_IMAGENONE,  EDIT_REMOVE,  TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 2},
    {I_IMAGENONE,  EDIT_LOAD,  TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 3},
    {I_IMAGENONE,  EDIT_SAVE,  TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 4},
    {I_IMAGENONE,  EDIT_SORT,  TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 5},
};

BOOL CZaxxonEditor::Initialize()
{
    int cxInitial = 300;
    int cyInitial = 500;
    _hwnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
        TEXT("ZaxxonEditor"), TEXT("Zaxxon Playlist Editor"),
        WS_CAPTION | WS_POPUPWINDOW | WS_THICKFRAME | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 
        0, 0, 0, 0,
        _pzax->GetHWND(), NULL, HINST_THISDLL, this);
    if (_hwnd)
    {
        _pzax->_pzax->Register(_hwnd);

        _hwndList = CreateWindow(
                TEXT("SysListView32"), TEXT(""),
                WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS, 
                0, 0, 0, 0,
                _hwnd, (HMENU)ID_LISTVIEW, NULL, NULL);

        if (_hwndList)
        {
            RECT rc;
            GetClientRect(_hwnd, &rc);
            LVCOLUMN lvc = {0};
            lvc.mask = LVCF_TEXT | LVCF_WIDTH;
            lvc.cx = 4 * cxInitial / 5 - 10;
            lvc.pszText = TEXT("Song");
            ListView_InsertColumn(_hwndList, 0, &lvc);

            lvc.mask = LVCF_TEXT | LVCF_WIDTH;
            lvc.cx = cxInitial / 5;
            lvc.pszText = TEXT("Length");
            ListView_InsertColumn(_hwndList, 1, &lvc);

            ListView_SetExtendedListViewStyle(_hwndList, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
        }

        _hwndToolbar = CreateWindowEx(WS_EX_TOOLWINDOW, TOOLBARCLASSNAME, NULL,
                             WS_VISIBLE | WS_CHILD | TBSTYLE_FLAT | TBSTYLE_LIST |
                             WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
                             CCS_NODIVIDER | CCS_NOPARENTALIGN |
                             CCS_NORESIZE | TBSTYLE_REGISTERDROP,
                             0, 0, 0, 0, _hwnd, (HMENU) ID_TOOLBAR, HINST_THISDLL, NULL);

        if (_hwndToolbar)
        {
            // Set the format to ANSI or UNICODE as appropriate.
            ToolBar_SetUnicodeFormat(_hwndToolbar, TRUE);
            SetWindowTheme(_hwndToolbar, L"TaskBar", NULL);
            SendMessage(_hwndToolbar, CCM_SETVERSION, COMCTL32_VERSION, 0);
            SendMessage(_hwndToolbar, TB_BUTTONSTRUCTSIZE,    SIZEOF(TBBUTTON), 0);
            SendMessage(_hwndToolbar, TB_ADDSTRING, NULL, (LPARAM)g_EditStrings);
            SendMessage(_hwndToolbar, TB_ADDBUTTONS, ARRAYSIZE(g_crgEditButtons), (LPARAM)&g_crgEditButtons);
            ToolBar_SetExtendedStyle(_hwndToolbar, TBSTYLE_EX_DOUBLEBUFFER, TBSTYLE_EX_DOUBLEBUFFER);
        }


        SetWindowPos(_hwnd, NULL, 0, 0, cxInitial, cyInitial, SWP_NOACTIVATE);

    }

    return _hwnd != NULL;
}

BOOL CZaxxonEditor::Show(BOOL fShow)
{
    ShowWindow(_hwnd, fShow?SW_SHOW:SW_HIDE);
    return TRUE;
}



LRESULT CZaxxonEditor::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        break;

    case WM_SONGCHANGE:
        {
            _fIgnoreChange = TRUE;
            HighlightSong((PTSTR)wParam);
            _fIgnoreChange = FALSE;
        }
        break;

    case WM_UPDATESONG:
        {
            UpdateSong((CSong*)wParam);
        }
        break;

    case WM_NOTIFY:
        {
            LPNMHDR pnm = (LPNMHDR)lParam;
            if (pnm->idFrom == ID_TOOLBAR)
            {
                if (pnm->code == NM_CLICK)
                {
                    {
                        int idCmd = (int)((LPNMCLICK)pnm)->dwItemSpec;
                        switch (idCmd)
                        {
                        case EDIT_NEW:
                            ClearPlaylist();
                            break;
                        case EDIT_ADD:
                            AddToPlaylist();
                            break;

                        case EDIT_REMOVE:
                            RemoveFromPlaylist();
                            break;

                        case EDIT_LOAD:
                            LoadPlaylist();
                            break;

                        case EDIT_SAVE:
                            SavePlaylist();
                            break;

                        case EDIT_SORT:
                            break;
                        }
                    }
                }
            }
            else if (pnm->idFrom == ID_LISTVIEW)
            {
                if (pnm->code == LVN_ITEMCHANGED && !_fIgnoreChange)
                {
                    NMLISTVIEW* pnml = (NMLISTVIEW*)pnm;
                    int iItem = pnml->iItem;
                    if (iItem < DPA_GetPtrCount(hSongList) &&
                        pnml->uNewState != pnml->uOldState &&
                        pnml->uNewState & LVIS_SELECTED)
                    {
                        _pzax->_pzax->SetSong(iItem);
                    }
                }
            }
        }
        break;

    case WM_CLOSE:
        ShowWindow(hwnd, SW_HIDE);
        return 1;

    case WM_SIZE:
        {
            RECT rc;
            GetClientRect(hwnd, &rc);
            LONG lButton = SendMessage(_hwndToolbar, TB_GETBUTTONSIZE, 0, 0L);
            SetWindowPos(_hwndList, NULL, rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc) - HIWORD(lButton), SWP_NOZORDER | SWP_NOACTIVATE);
            SetWindowPos(_hwndToolbar, NULL, rc.left, RECTHEIGHT(rc) - HIWORD(lButton), RECTWIDTH(rc), HIWORD(lButton), SWP_NOZORDER | SWP_NOACTIVATE);
        }
        break;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


LRESULT CZaxxonEditor::s_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (uMsg == WM_CREATE)
    {
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (ULONG_PTR)((CREATESTRUCT*)lParam)->lpCreateParams);
    }

    CZaxxonEditor* pedit = (CZaxxonEditor*)GetWindowLongPtr(hwnd, GWLP_USERDATA);


    if (pedit)
        return pedit->WndProc(hwnd, uMsg, wParam, lParam);

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


CSongExtractionTask::CSongExtractionTask(HWND hwnd, CSong* psong)
    : CRunnableTask(RTF_DEFAULT), _hwnd(hwnd)
{
    _psong = psong;
    _psong->AddRef();
}

CSongExtractionTask::~CSongExtractionTask()
{
    _psong->Release();
}

STDMETHODIMP CSongExtractionTask::RunInitRT()
{
    LPITEMIDLIST pidl = ILCreateFromPath(_psong->szSong);
    if (pidl)
    {
        LPCITEMIDLIST pidlChild;
        IShellFolder2* psf;

        HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder2, &psf), &pidlChild);

        if (SUCCEEDED(hr))
        {
            VARIANT v;
            hr = psf->GetDetailsEx(pidlChild, &SCID_MUSIC_Artist, &v);
            if (SUCCEEDED(hr))
            {
                VariantToStr(&v, _psong->szArtist, ARRAYSIZE(_psong->szArtist));
            }

            hr = psf->GetDetailsEx(pidlChild, &SCID_MUSIC_Album, &v);
            if (SUCCEEDED(hr))
            {
                VariantToStr(&v, _psong->szAlbum, ARRAYSIZE(_psong->szAlbum));
            }

            hr = psf->GetDetailsEx(pidlChild, &SCID_Title, &v);
            if (SUCCEEDED(hr))
            {
                VariantToStr(&v, _psong->szTitle, ARRAYSIZE(_psong->szTitle));
            }

            hr = psf->GetDetailsEx(pidlChild, &SCID_AUDIO_Duration, &v);
            if (SUCCEEDED(hr))
            {
                PROPVARIANT pv = *(PROPVARIANT*)&v;
                FILETIME ft = {pv.uhVal.LowPart, pv.uhVal.HighPart};
                SYSTEMTIME st;
                FileTimeToSystemTime(&ft, &st);

                GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, 
                    &st, NULL, _psong->szDuration, ARRAYSIZE(_psong->szDuration));
            }

            SendMessage(_hwnd, WM_UPDATESONG, (WPARAM)_psong, 0);

            psf->Release();
        }

        ILFree(pidl);
    }

    return S_OK;
}