If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// ed_tilesets.cpp

#include "editor.h"

#include "rttspanels.h"

#include "gui_listbox.h"
#include "gui_grid.h"

/*
==============================================================================

EdTilePool IMPLEMENTATION

==============================================================================
*/

/*
==============
EdTilePool::EdTilePool

Initialize the pool
==============
*/
EdTilePool::EdTilePool(const char *pszName)
{
    m_pszName = L_Strdup(pszName, TAG_EDITOR);
}

/*
==============
EdTilePool::~EdTilePool

Free allocate resources
==============
*/
EdTilePool::~EdTilePool()
{
    L_Free(m_pszName);
}

/*
==============================================================================

EdTilesets IMPLEMENTATION

==============================================================================
*/

/*
==============
EdTilesets::EdTilesets

Start out with no tilesets
==============
*/
EdTilesets::EdTilesets()
{
    m_iNumTypesEx = 0;
    m_pTypesEx = 0;

    m_iNumBorderNames = 0;
    m_pBorderNames = 0;

    m_iNumTilesets = 0;
    m_ppTilesets = 0;

    m_pCurrent = 0;
    m_iCurIdx = -1;
    m_iCurTile = ~0;
}

/*
==============
EdTilesets::~EdTilesets

Free all allocated data
==============
*/
EdTilesets::~EdTilesets()
{
    int i;

    if (m_pTypesEx)
        L_Free(m_pTypesEx);

    if (m_iNumBorderNames) {
        for(i = 0; i < m_iNumBorderNames; i++)
            L_Free(m_pBorderNames[i]);
        L_Free(m_pBorderNames);
    }

    if (m_iNumTilesets) {
        for(i = 0; i < m_iNumTilesets; i++)
            delete m_ppTilesets[i];
        L_Free(m_ppTilesets);
    }
}

/*
==============
EdTilesets::LoadTileset

Parse the tileset definition file
==============
*/
void EdTilesets::LoadTileset(const char *name)
{
    char path[MAX_OSPATH];
    LParser p;
    EdTilePool *tp;
    ed_tiletype_t *ett;
    const char *token;
    unsigned id;
    bool valid;
    int dir, border;

    snprintf(path, sizeof(path), "tiles/%s.ts", name);
    p.AddFile(path);

    // Create the new pool
    tp = new EdTilePool(name);

    m_iNumTilesets++;
    m_ppTilesets = (EdTilePool **)L_Realloc(m_ppTilesets, m_iNumTilesets*sizeof(void *),
                TAG_EDITOR);
    m_ppTilesets[m_iNumTilesets-1] = tp;

    // Parse the file
    for(;;) {
        // token is always the tilename
        token = p.TryString();
        if (!token)
            break;

        id = m_tiletypes.Load(token);
        valid = m_tiletypes.ValidId(id);

        if (valid) {
            tp->m_ids.Add(id);
            ett = GetTileTypeEx(id);
        } else
            ett = 0;

        // parse border types
        p.Expect("(");

        for(;;) {
            p.NextToken();
            if (p.tok.type == ')')
                break;
            if (p.tok.type != TOK_STRING)
                throw p.Error("'(' or keyword expected");

            if (!strcmp(p.tok.string, "noauto")) {
                if (ett)
                    ett->noauto = true;
            } else {
                if (!strcmp(p.tok.string, "left"))
                    dir = ttb_left;
                else if (!strcmp(p.tok.string, "right"))
                    dir = ttb_right;
                else if (!strcmp(p.tok.string, "bottom"))
                    dir = ttb_bottom;
                else if (!strcmp(p.tok.string, "top"))
                    dir = ttb_top;
                else
                    throw p.Error("unknown keyword %s", p.tok.string);

                token = p.AnyString();

                if (ett) {
                    border = GetBorderByName(token);
                    if (ett->border[dir] < 0)
                        ett->border[dir] = border;
                    else if (ett->border[dir] != border)
                        L_Printf("%s: conflicting border %i: %s\n",
                                m_tiletypes.m_pTT[id].pic->m_szName, dir, token);
                }
            }
        }

        p.Expect(";");
    }
}

/*
==============
EdTilesets::Load

Scan the tiles directory for available tiles
==============
*/
void EdTilesets::Load()
{
    char **names, *p;
    int count, i;

    // Load all tiles that are available
    count = L_FindFiles("tiles", "*.bmp", &names);

    for(i = 0; i < count; i++) {
        p = strstr(names[i], ".bmp");
        if (p)
            *p = 0;

        m_tiletypes.Load(names[i]);
    }

    L_FreeFindFiles(names);

    // Load tileset descriptions
    count = L_FindFiles("tiles", "*.ts", &names);

    for(i = 0; i < count; i++) {
        try {
            p = strstr(names[i], ".ts");
            if (p)
                *p = 0;

            LoadTileset(names[i]);
        } catch(LError &err) {
            L_Printf("Error loading %s.ts: %s\n", names[i], err.Get());
        }
    }

    L_FreeFindFiles(names);
}

/*
==============
EdTilesets::GetBorderByName

Return the border ID used for auto-placing, create a new ID if the name's unique.
==============
*/
int EdTilesets::GetBorderByName(const char *name)
{
    int i;

    for(i = 0; i < m_iNumBorderNames; i++) {
        if (!strcmp(m_pBorderNames[i], name))
            return i;
    }

    i = m_iNumBorderNames++;
    m_pBorderNames = (char **)L_Realloc(m_pBorderNames, sizeof(char *)*(m_iNumBorderNames),
                TAG_EDITOR);
    m_pBorderNames[i] = L_Strdup(name, TAG_EDITOR);

    return i;
}

/*
==============
EdTilesets::GetTileTypeEx

Return the extended, editor-only information for the given tile.
If necessary, the information is created using default values
==============
*/
ed_tiletype_t *EdTilesets::GetTileTypeEx(unsigned id)
{
    ed_tiletype_t *ett;

    lassert(m_tiletypes.ValidId(id));

    if (id >= m_iNumTypesEx) {
        m_pTypesEx = (ed_tiletype_t *)L_Realloc(m_pTypesEx, sizeof(ed_tiletype_t)*(id+1),
            TAG_EDITOR);

        for(ett = &m_pTypesEx[m_iNumTypesEx]; m_iNumTypesEx <= id; m_iNumTypesEx++, ett++) {
            ett->border[0] = ett->border[1] = ett->border[2] = ett->border[3] = -1;
            ett->noauto = false;
        }
    }

    return &m_pTypesEx[id];
}


/*
==============
EdTilesets::isCurTileValid

Returns true if the currently selected tile is valid for painting
==============
*/
bool EdTilesets::isCurTileValid()
{
    return m_tiletypes.ValidId(m_iCurTile);
}

/*
==============
EdTilesets::SetTileset

Sets the current tileset, based on ID (-1 means all tiles)
==============
*/
void EdTilesets::SetTileset(int id)
{
    if (id < 0 || id >= m_iNumTilesets)
        m_pCurrent = 0;
    else
        m_pCurrent = m_ppTilesets[id];
}

/*
==============
EdTilesets::SetCurTile

Set the current tile; adjust m_iCurIdx if possible
==============
*/
void EdTilesets::SetCurTile(unsigned id)
{
    lassert(m_tiletypes.ValidId(id));

    m_iCurTile = id;

    if (!m_pCurrent)
        m_iCurIdx = id;
    else
        m_iCurIdx = m_pCurrent->m_ids.Find(id);
}

/*
==============
EdTilesets::SetCurIdx

Sets the current tile, where idx is relative to the currently selected
tileset
==============
*/
void EdTilesets::SetCurIdx(int idx)
{
    m_iCurIdx = idx;

    if (!m_pCurrent)
        m_iCurTile = idx;
    else
        m_iCurTile = m_pCurrent->m_ids.items[idx];
}

/*
==============
EdTilesets::NextTile
EdTilesets::PrevTile

Cycle the selected tile
==============
*/
void EdTilesets::NextTile()
{
    m_iCurIdx++;
    if (!m_pCurrent) {
        if ((unsigned)m_iCurIdx >= m_tiletypes.m_iNumTT)
            m_iCurIdx = 0;
        m_iCurTile = m_iCurIdx;
    } else {
        if (m_iCurIdx >= m_pCurrent->m_ids.size)
            m_iCurIdx = 0;
        m_iCurTile = m_pCurrent->m_ids.items[m_iCurIdx];
    }
}

void EdTilesets::PrevTile()
{
    m_iCurIdx--;
    if (!m_pCurrent) {
        if (m_iCurIdx < 0)
            m_iCurIdx = m_tiletypes.m_iNumTT - 1;
        m_iCurTile = m_iCurIdx;
    } else {
        if (m_iCurIdx < 0)
            m_iCurIdx = m_pCurrent->m_ids.size - 1;
        m_iCurTile = m_pCurrent->m_ids.items[m_iCurIdx];
    }
}

/*
==============================================================================

ChooseTilesetDlg IMPLEMENTATION

==============================================================================
*/

/*
Returns -1 for cancel, and the selected tileset (1-based) when run;
A return value of 0 means "all" has been selected.
*/
class ChooseTilesetDlg : public RttsDialog {
public:
    GListBox    *m_pListBox;
    GButton     *m_pOk;

public:
    ChooseTilesetDlg(GPanel *pParent, EdTilesets *pTilesets);

    void Refresh();

    void Ok(int id);
};

ChooseTilesetDlg::ChooseTilesetDlg(GPanel *pParent, EdTilesets *pTilesets)
    : RttsDialog(pParent, 50, 50, 540, 380)
{
    GButton *btn;
    int i;

    m_pListBox = new GListBox(this, 30, 30, 330, 320);
    m_pListBox->SetColor(192, 192, 192);
    m_pListBox->SetSelectedColor(255, 255, 0);

    m_pListBox->AddString("<all>");

    for(i = 0; i < pTilesets->m_iNumTilesets; i++)
        m_pListBox->AddString(pTilesets->m_ppTilesets[i]->m_pszName);

    m_pListBox->SelectChanged.Connect(this, &ChooseTilesetDlg::Refresh);

    m_pOk = new RttsButton(this, 450, 30, 0, 30, "Ok", halign_center);
    m_pOk->Clicked.Connect(this, &ChooseTilesetDlg::Ok);

    btn = new RttsButton(this, 450, 60, 0, 30, "Cancel", halign_center, -1);
    btn->Clicked.Connect(this, &ChooseTilesetDlg::EndModal);

    Refresh();
}

void ChooseTilesetDlg::Refresh()
{
    bool valid;

    valid = true;
    if (!m_pListBox->GetSelection())
        valid = false;

    m_pOk->SetOblivious(!valid);
}

void ChooseTilesetDlg::Ok(int id)
{
    EndModal(m_pListBox->GetSelectionId());
}


/*
==============================================================================

PickTileDlg IMPLEMENTATION

==============================================================================
*/

class PickTileDlg : public RttsDialog {
public:
    EdTilesets  *m_pTilesets;

    GGrid       *m_pGrid;

public:
    PickTileDlg(GPanel *pParent, EdTilesets *pTilesets);

    void Refresh();

    void ChooseTileset(int id);
};

PickTileDlg::PickTileDlg(GPanel *pParent, EdTilesets *pTilesets)
    : RttsDialog(pParent, 30, 30, 580, 420)
{
    GButton *btn;

    btn = new RttsButton(this, 290, 30, 0, 0, "Choose tileset", halign_center);
    btn->Clicked.Connect(this, &PickTileDlg::ChooseTileset);

    m_pTilesets = pTilesets;
    m_pGrid = 0;

    Refresh();
}

void PickTileDlg::Refresh()
{
    GButton *btn;
    int count, i;
    unsigned id;

    if (m_pGrid)
        delete m_pGrid;

    m_pGrid = new GGrid(this, 30, 60, 520, 330);

    if (m_pTilesets->m_pCurrent)
        count = m_pTilesets->m_pCurrent->m_ids.size;
    else
        count = m_pTilesets->m_tiletypes.m_iNumTT;

    for(i = 0; i < count; i++) {
        if (m_pTilesets->m_pCurrent)
            id = m_pTilesets->m_pCurrent->m_ids.items[i];
        else
            id = i;

        btn = new GButton(m_pGrid, 1, 1, 40, 40, 0, halign_center|valign_center, i);
        btn->SetPicture(m_pTilesets->m_tiletypes.m_pTT[id].pic);
        btn->SetClickedColor(255, 0, 0);
        btn->Clicked.Connect(this, &PickTileDlg::EndModal);
    }

    m_pGrid->Arrange(true, true);
}

void PickTileDlg::ChooseTileset(int id)
{
    ChooseTilesetDlg dlg(this, m_pTilesets);
    int code;

    code = dlg.Run();

    if (code >= 0)
        m_pTilesets->SetTileset(code-1);

    Refresh();
}


/*
==============================================================================

Editor

==============================================================================
*/

/*
==============
Editor::Tile_Pick

Open the pick tile dialog
==============
*/
void Editor::Tile_Pick()
{
    lassert(m_iMode == tlm_tile);

    int code;
    PickTileDlg dlg(this, &m_tilesets);

    code = dlg.Run();

    if (code < 0)
        return;

    m_tilesets.SetCurIdx(code);
}