/*
 * This file is part of the Colobot: Gold Edition source code
 * Copyright (C) 2001-2023, Daniel Roux, EPSITEC SA & TerranovaTeam
 * http://epsitec.ch; http://colobot.info; http://github.com/colobot
 *
 * 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://gnu.org/licenses
 */

#include "ui/screen/screen_level_list.h"

#include "app/app.h"

#include "common/settings.h"

#include "common/resources/resourcemanager.h"

#include "level/player_profile.h"

#include "level/parser/parser.h"

#include "ui/maindialog.h"

#include "ui/controls/button.h"
#include "ui/controls/check.h"
#include "ui/controls/edit.h"
#include "ui/controls/interface.h"
#include "ui/controls/label.h"
#include "ui/controls/list.h"
#include "ui/controls/window.h"

#include <algorithm>

namespace Ui
{

CScreenLevelList::CScreenLevelList(CMainDialog* mainDialog)
    : m_dialog(mainDialog),
      m_category{},
      m_sceneSoluce{false},
      m_plusTrainer{false},
      m_plusResearch{false},
      m_plusExplorer{false},
      m_maxList{0},
      m_accessChap{0}
{
}

void CScreenLevelList::SetLevelCategory(LevelCategory category)
{
    m_category = category;
}

void CScreenLevelList::CreateInterface()
{
    CWindow*        pw;
    CEdit*          pe;
    CLabel*         pl;
    CButton*        pb;
    CCheck*         pc;
    CList*          pli;
    Math::Point     pos, ddim;
    int             res;
    std::string     name;

    if ( m_category == LevelCategory::FreeGame )
    {
        m_accessChap = m_main->GetPlayerProfile()->GetChapPassed(LevelCategory::Missions);
    }

    pos.x = 0.10f;
    pos.y = 0.10f;
    ddim.x = 0.80f;
    ddim.y = 0.80f;
    pw = m_interface->CreateWindows(pos, ddim, 12, EVENT_WINDOW5);
    pw->SetClosable(true);
    if ( m_category == LevelCategory::Exercises    )  res = RT_TITLE_TRAINER;
    if ( m_category == LevelCategory::Challenges   )  res = RT_TITLE_DEFI;
    if ( m_category == LevelCategory::Missions     )  res = RT_TITLE_MISSION;
    if ( m_category == LevelCategory::FreeGame     )  res = RT_TITLE_FREE;
    if ( m_category == LevelCategory::CodeBattles  )  res = RT_TITLE_CODE_BATTLES;
    if ( m_category == LevelCategory::GamePlus     )  res = RT_TITLE_PLUS;
    if ( m_category == LevelCategory::CustomLevels )  res = RT_TITLE_USER;
    GetResource(RES_TEXT, res, name);
    pw->SetName(name);

    pos.x  = 0.10f;
    pos.y  = 0.40f;
    ddim.x = 0.50f;
    ddim.y = 0.50f;
    pw->CreateGroup(pos, ddim, 5, EVENT_INTERFACE_GLINTl);  // orange corner
    pos.x  = 0.40f;
    pos.y  = 0.10f;
    ddim.x = 0.50f;
    ddim.y = 0.50f;
    pw->CreateGroup(pos, ddim, 4, EVENT_INTERFACE_GLINTr);  // blue corner

    // Displays a list of chapters:
    pos.x = ox+sx*3;
    pos.y = oy+sy*10.5f;
    ddim.x = dim.x*7.5f;
    ddim.y = dim.y*0.6f;
    res = RT_PLAY_CHAP_CHAPTERS;
    if ( m_category == LevelCategory::Missions     )  res = RT_PLAY_CHAP_PLANETS;
    if ( m_category == LevelCategory::FreeGame     )  res = RT_PLAY_CHAP_PLANETS;
    if ( m_category == LevelCategory::GamePlus     )  res = RT_PLAY_CHAP_PLANETS;
    if ( m_category == LevelCategory::CustomLevels )  res = RT_PLAY_CHAP_USERLVL;
    GetResource(RES_TEXT, res, name);
    pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL11, name);
    pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);

    pos.y = oy+sy*6.7f;
    ddim.y = dim.y*4.5f;
    ddim.x = dim.x*6.5f;
    pli = pw->CreateList(pos, ddim, 0, EVENT_INTERFACE_CHAP);
    pli->SetState(STATE_SHADOW);
    m_chap[m_category] = m_main->GetPlayerProfile()->GetSelectedChap(m_category)-1;
    UpdateSceneChap(m_chap[m_category]);
    if ( m_category != LevelCategory::FreeGame &&
         m_category != LevelCategory::CodeBattles &&
         m_category != LevelCategory::CustomLevels ) // Don't show completion marks in free game, code battles and userlevels
    {
        pli->SetState(STATE_EXTEND);
    }

    // Displays a list of missions:
    pos.x = ox+sx*9.5f;
    pos.y = oy+sy*10.5f;
    ddim.x = dim.x*7.5f;
    ddim.y = dim.y*0.6f;
    res = RT_PLAY_LIST_LEVELS;
    if ( m_category == LevelCategory::Exercises    )  res = RT_PLAY_LIST_EXERCISES;
    if ( m_category == LevelCategory::Challenges   )  res = RT_PLAY_LIST_CHALLENGES;
    if ( m_category == LevelCategory::Missions     )  res = RT_PLAY_LIST_MISSIONS;
    if ( m_category == LevelCategory::FreeGame     )  res = RT_PLAY_LIST_FREEGAME;
    if ( m_category == LevelCategory::GamePlus     )  res = RT_PLAY_LIST_MISSIONS;
    GetResource(RES_TEXT, res, name);
    pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL12, name);
    pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);

    pos.y = oy+sy*6.7f;
    ddim.y = dim.y*4.5f;
    ddim.x = dim.x*6.5f;
    pli = pw->CreateList(pos, ddim, 0, EVENT_INTERFACE_LIST);
    pli->SetState(STATE_SHADOW);
    m_sel[m_category] = m_main->GetPlayerProfile()->GetSelectedRank(m_category)-1;
    UpdateSceneList(m_chap[m_category], m_sel[m_category]);
    if ( m_category != LevelCategory::FreeGame &&
         m_category != LevelCategory::CodeBattles &&
         m_category != LevelCategory::CustomLevels ) // Don't show completion marks in free game, code battles and userlevels
    {
        pli->SetState(STATE_EXTEND);
    }
    pos = pli->GetPos();
    ddim = pli->GetDim();

    // Displays the summary:
    pos.x = ox+sx*3;
    pos.y = oy+sy*5.4f;
    ddim.x = dim.x*6.5f;
    ddim.y = dim.y*0.6f;
    GetResource(RES_TEXT, RT_PLAY_RESUME, name);
    pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL13, name);
    pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);

    pos.x = ox+sx*3;
    pos.y = oy+sy*3.6f;
    ddim.x = dim.x*13.4f;
    ddim.y = dim.y*1.9f;
    pe = pw->CreateEdit(pos, ddim, 0, EVENT_INTERFACE_RESUME);
    pe->SetState(STATE_SHADOW);
    pe->SetMaxChar(500);
    pe->SetEditCap(false);  // just to see
    pe->SetHighlightCap(false);

    // Button displays the "soluce":
    if ( m_category != LevelCategory::Exercises &&
         m_category != LevelCategory::GamePlus  &&
         m_category != LevelCategory::FreeGame   )
    {
        pos.x = ox+sx*9.5f;
        pos.y = oy+sy*5.8f;
        ddim.x = dim.x*6.5f;
        ddim.y = dim.y*0.5f;
        pc = pw->CreateCheck(pos, ddim, -1, EVENT_INTERFACE_SOLUCE);
        pc->SetState(STATE_SHADOW);
        pc->ClearState(STATE_CHECK);
    }
    m_sceneSoluce = false;

    if ( m_category == LevelCategory::GamePlus )
    {
        pos.x = ox+sx*9.5f;
        pos.y = oy+sy*6.1f;
        ddim.x = dim.x*3.4f;
        ddim.y = dim.y*0.5f;
        pc = pw->CreateCheck(pos, ddim, -1, EVENT_INTERFACE_PLUS_TRAINER);
        pc->SetState(STATE_SHADOW);
        pc->ClearState(STATE_CHECK);

        pos.y = oy+sy*5.5f;
        pc = pw->CreateCheck(pos, ddim, -1, EVENT_INTERFACE_PLUS_RESEARCH);
        pc->SetState(STATE_SHADOW);
        pc->ClearState(STATE_CHECK);

        pos.x = ox+sx*12.9f;
        pos.y = oy+sy*6.1f;
        pc = pw->CreateCheck(pos, ddim, -1, EVENT_INTERFACE_PLUS_EXPLORER);
        pc->SetState(STATE_SHADOW);
        pc->ClearState(STATE_CHECK);
    }
    m_plusTrainer  = false;
    m_plusResearch = false;
    m_plusExplorer = false;

    UpdateSceneResume(m_chap[m_category]+1, m_sel[m_category]+1);

    if ( m_category == LevelCategory::Missions    ||
         m_category == LevelCategory::FreeGame    ||
         m_category == LevelCategory::GamePlus    ||
         m_category == LevelCategory::CustomLevels )
    {
        pos.x = ox+sx*9.5f;
        pos.y = oy+sy*2;
        ddim.x = dim.x*3.7f;
        ddim.y = dim.y*1;
        pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_PLAY);
        pb->SetState(STATE_SHADOW);
        if ( m_maxList == 0 )
        {
            pb->ClearState(STATE_ENABLE);
        }

        pos.x += dim.x*4.0f;
        ddim.x = dim.x*2.5f;
        pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_READ);
        pb->SetState(STATE_SHADOW);
        if ( !m_main->GetPlayerProfile()->HasAnySavedScene() )  // no file to read?
        {
            pb->ClearState(STATE_ENABLE);
        }
    }
    else
    {
        pos.x = ox+sx*9.5f;
        pos.y = oy+sy*2;
        ddim.x = dim.x*6.5f;
        ddim.y = dim.y*1;
        pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_PLAY);
        pb->SetState(STATE_SHADOW);
        if ( m_maxList == 0 )
        {
            pb->ClearState(STATE_ENABLE);
        }
    }

    pos.x = ox+sx*3;
    ddim.x = dim.x*4;
    pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_BACK);
    pb->SetState(STATE_SHADOW);

    SetBackground("textures/interface/interface.png");
    CreateVersionDisplay();

    if (m_category == LevelCategory::CustomLevels)
    {
        if(m_customLevelList.size() == 0)
        {
            m_main->ChangePhase(PHASE_MAIN_MENU);
            std::string title, text;
            GetResource(RES_TEXT, RT_DIALOG_NOUSRLVL_TITLE, title);
            GetResource(RES_TEXT, RT_DIALOG_NOUSRLVL_TEXT, text);
            m_dialog->StartInformation(title, title, text);
        }
    }
}

bool CScreenLevelList::EventProcess(const Event &event)
{
    CWindow* pw;
    CList* pl;
    CButton* pb;

    pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
    if ( pw == nullptr )  return false;

    if ( event.type == pw->GetEventTypeClose() ||
         event.type == EVENT_INTERFACE_BACK    ||
         (event.type == EVENT_KEY_DOWN && event.GetData<KeyEventData>()->key == KEY(ESCAPE)) )
    {
        m_main->ChangePhase(PHASE_MAIN_MENU);
        return false;
    }

    switch( event.type )
    {
        case EVENT_INTERFACE_CHAP:
            pl = static_cast<CList*>(pw->SearchControl(EVENT_INTERFACE_CHAP));
            if ( pl == nullptr )  break;
            m_chap[m_category] = pl->GetSelect();
            m_main->GetPlayerProfile()->SetSelectedChap(m_category, m_chap[m_category]+1);
            UpdateSceneList(m_chap[m_category], m_sel[m_category]);
            UpdateSceneResume(m_chap[m_category]+1, m_sel[m_category]+1);
            break;

        case EVENT_INTERFACE_LIST:
            pl = static_cast<CList*>(pw->SearchControl(EVENT_INTERFACE_LIST));
            if ( pl == nullptr )  break;
            m_sel[m_category] = pl->GetSelect();
            m_main->GetPlayerProfile()->SetSelectedRank(m_category, m_sel[m_category]+1);
            UpdateSceneResume(m_chap[m_category]+1, m_sel[m_category]+1);
            break;

        case EVENT_INTERFACE_SOLUCE:
            pb = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_SOLUCE));
            if ( pb == nullptr )  break;
            m_sceneSoluce = !m_sceneSoluce;
            pb->SetState(STATE_CHECK, m_sceneSoluce);
            break;

        case EVENT_INTERFACE_PLUS_TRAINER:
            pb = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_PLUS_TRAINER));
            if ( pb == nullptr )  break;
            m_plusTrainer = !m_plusTrainer;
            pb->SetState(STATE_CHECK, m_plusTrainer);
            break;

        case EVENT_INTERFACE_PLUS_RESEARCH:
            pb = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_PLUS_RESEARCH));
            if ( pb == nullptr )  break;
            m_plusResearch = !m_plusResearch;
            pb->SetState(STATE_CHECK, m_plusResearch);
            break;

        case EVENT_INTERFACE_PLUS_EXPLORER:
            pb = static_cast<CButton*>(pw->SearchControl(EVENT_INTERFACE_PLUS_EXPLORER));
            if ( pb == nullptr )  break;
            m_plusExplorer = !m_plusExplorer;
            pb->SetState(STATE_CHECK, m_plusExplorer);
            break;

        case EVENT_INTERFACE_PLAY:
            m_main->SetLevel(m_category, m_chap[m_category]+1, m_sel[m_category]+1);
            m_main->ChangePhase(PHASE_SIMUL);
            break;

        case EVENT_INTERFACE_READ:
            m_main->ChangePhase(PHASE_READ);
            break;

        default:
            return true;
    }
    return false;
}

void CScreenLevelList::SetSelection(LevelCategory category, int chap, int rank)
{
    m_chap[category] = chap;
    m_sel[category] = rank;
}

// Updates the lists according to the cheat code.

void CScreenLevelList::AllMissionUpdate()
{
    UpdateSceneChap(m_chap[m_category]);
    UpdateSceneList(m_chap[m_category], m_sel[m_category]);
}

// Whether to show the solution.

bool CScreenLevelList::GetSceneSoluce()
{
    return m_sceneSoluce;
}

bool CScreenLevelList::GetPlusTrainer()
{
    return m_plusTrainer;
}

bool CScreenLevelList::GetPlusResearch()
{
    return m_plusResearch;
}

bool CScreenLevelList::GetPlusExplorer()
{
    return m_plusExplorer;
}

// Updates the chapters of exercises or missions.

void CScreenLevelList::UpdateSceneChap(int &chap)
{
    CWindow*    pw;
    CList*      pl;

    std::string fileName;
    char        line[500] = {0};
    bool        bPassed;

    pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
    if ( pw == nullptr )  return;
    pl = static_cast<CList*>(pw->SearchControl(EVENT_INTERFACE_CHAP));
    if ( pl == nullptr )  return;

    pl->Flush();

    int j;
    if ( m_category == LevelCategory::CustomLevels )
    {
        UpdateCustomLevelList();

        for ( j=0 ; j < static_cast<int>(m_customLevelList.size()) ; j++ )
        {
            try
            {
                CLevelParser levelParser("custom", j+1, 0);
                levelParser.Load();
                pl->SetItemName(j, levelParser.Get("Title")->GetParam("text")->AsString());
                pl->SetEnable(j, true);
            }
            catch (CLevelParserException& e)
            {
                pl->SetItemName(j, std::string("[ERROR]: ")+e.what());
                pl->SetEnable(j, false);
            }
        }
    }
    else
    {
        for ( j=0 ; j<MAXSCENE ; j++ )
        {
            CLevelParser levelParser(m_category, j+1, 0);
            if (!levelParser.Exists())
                break;
            try
            {
                levelParser.Load();
                sprintf(line, "%d: %s", j+1, levelParser.Get("Title")->GetParam("text")->AsString().c_str());
            }
            catch (CLevelParserException& e)
            {
                sprintf(line, "%s", (std::string("[ERROR]: ")+e.what()).c_str());
            }

            bPassed = m_main->GetPlayerProfile()->GetLevelPassed(m_category, j+1, 0);
            pl->SetItemName(j, line);
            pl->SetCheck(j, bPassed);
            pl->SetEnable(j, true);

            if ( (m_category == LevelCategory::Missions || m_category == LevelCategory::GamePlus) && !m_main->GetShowAll() && !bPassed )
            {
                j ++;
                break;
            }

            if ( m_category == LevelCategory::FreeGame && j == m_accessChap )
            {
                j ++;
                break;
            }
        }
    }

    if ( chap > j-1 )  chap = j-1;

    pl->SetSelect(chap);
    pl->ShowSelect(false);  // shows the selected columns
}

// Updates the list of exercises or missions.

void CScreenLevelList::UpdateSceneList(int chap, int &sel)
{
    CWindow*    pw;
    CList*      pl;
    std::string fileName;
    char        line[500] = {0};
    int         j;
    bool        bPassed;

    pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
    if ( pw == nullptr )  return;
    pl = static_cast<CList*>(pw->SearchControl(EVENT_INTERFACE_LIST));
    if ( pl == nullptr )  return;

    pl->Flush();

    bool readAll = true;
    for ( j=0 ; j<MAXSCENE ; j++ )
    {
        CLevelParser levelParser(m_category, chap+1, j+1);
        if (!levelParser.Exists())
        {
            readAll = true;
            break;
        }
        else
        {
            if (!readAll)
                break;
        }
        try
        {
            levelParser.Load();
            sprintf(line, "%d: %s", j+1, levelParser.Get("Title")->GetParam("text")->AsString().c_str());
        }
        catch (CLevelParserException& e)
        {
            sprintf(line, "%s", (std::string("[ERROR]: ")+e.what()).c_str());
        }

        bPassed = m_main->GetPlayerProfile()->GetLevelPassed(m_category, chap+1, j+1);
        pl->SetItemName(j, line);
        pl->SetCheck(j, bPassed);
        pl->SetEnable(j, true);

        if ( (m_category == LevelCategory::Missions || m_category == LevelCategory::GamePlus) && !m_main->GetShowAll() && !bPassed )
        {
            readAll = false;
        }
    }

    if (readAll)
    {
        m_maxList = j;
    }
    else
    {
        m_maxList = j+1;  // this is not the last!
    }

    if ( sel > j-1 )  sel = j-1;

    pl->SetSelect(sel);
    pl->ShowSelect(false);  // shows the selected columns
}

// Updates the button "solution" according to cheat code.

void CScreenLevelList::ShowSoluceUpdate()
{
    CWindow*    pw;
    CEdit*      pe;
    CCheck*     pc;

    m_sceneSoluce = false;

    pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
    if ( pw == nullptr )  return;
    pe = static_cast<CEdit*>(pw->SearchControl(EVENT_INTERFACE_RESUME));
    if ( pe == nullptr )  return;
    pc = static_cast<CCheck*>(pw->SearchControl(EVENT_INTERFACE_SOLUCE));
    if ( pc == nullptr )  return;

    if ( m_main->GetShowSoluce() )
    {
        pc->SetState(STATE_VISIBLE);
        pc->SetState(STATE_CHECK);
        m_sceneSoluce = true;
    }
    else
    {
        pc->ClearState(STATE_VISIBLE);
        pc->ClearState(STATE_CHECK);
        m_sceneSoluce = false;
    }
}

// Updates a summary of exercise or mission.

void CScreenLevelList::UpdateSceneResume(int chap, int rank)
{
    CWindow*    pw;
    CEdit*      pe;
    CCheck*     pc;
    std::string fileName;
    int         numTry;
    bool        bPassed, bVisible;

    pw = static_cast<CWindow*>(m_interface->SearchControl(EVENT_WINDOW5));
    if ( pw == nullptr )  return;
    pe = static_cast<CEdit*>(pw->SearchControl(EVENT_INTERFACE_RESUME));
    if ( pe == nullptr )  return;
    pc = static_cast<CCheck*>(pw->SearchControl(EVENT_INTERFACE_SOLUCE));

    if ( pc == nullptr )
    {
        m_sceneSoluce = false;
    }
    else
    {
        numTry  = m_main->GetPlayerProfile()->GetLevelTryCount(m_category, chap, rank);
        bPassed = m_main->GetPlayerProfile()->GetLevelPassed(m_category, chap, rank);
        bVisible = ( numTry > 2 || bPassed || m_main->GetShowSoluce() );
        if ( !CSettings::GetInstancePointer()->GetSoluce4() )  bVisible = false;
        pc->SetState(STATE_VISIBLE, bVisible);
        if ( !bVisible )
        {
            pc->ClearState(STATE_CHECK);
            m_sceneSoluce = false;
        }
    }

    if(chap == 0 || rank == 0) return;

    try
    {
        CLevelParser levelParser(m_category, chap, rank);
        levelParser.Load();
        pe->SetText(levelParser.Get("Resume")->GetParam("text")->AsString().c_str());
    }
    catch (CLevelParserException& e)
    {
        pe->SetText((std::string("[ERROR]: ")+e.what()).c_str());
    }
}

void CScreenLevelList::UpdateChapterPassed()
{
    // TODO: CScreenLevelList is a bad place for this function
    bool bAll = true;
    for ( int i=0 ; i<m_maxList ; i++ )
    {
        if (!m_main->GetPlayerProfile()->GetLevelPassed(m_category, m_chap[m_category]+1, i+1))
        {
            bAll = false;
            break;
        }
    }
    m_main->GetPlayerProfile()->IncrementLevelTryCount(m_category, m_chap[m_category]+1, 0);
    m_main->GetPlayerProfile()->SetLevelPassed(m_category, m_chap[m_category]+1, 0, bAll);
}


// Passes to the next mission, and possibly in the next chapter.

void CScreenLevelList::NextMission()
{
    m_sel[m_category] ++;  // next mission

    if ( m_sel[m_category] >= m_maxList )  // last mission of the chapter?
    {
        m_chap[m_category] ++;  // next chapter
        m_sel[m_category] = 0;  // first mission
    }

    m_main->GetPlayerProfile()->SetSelectedChap(m_category, m_chap[m_category]+1);
    m_main->GetPlayerProfile()->SetSelectedRank(m_category, m_sel[m_category]+1);
}

// TODO: Separate class for userlevels?
void CScreenLevelList::UpdateCustomLevelList()
{
    auto userLevelDirs = CResourceManager::ListDirectories("levels/custom/");
    std::sort(userLevelDirs.begin(), userLevelDirs.end());
    m_customLevelList = userLevelDirs;
}

std::string CScreenLevelList::GetCustomLevelName(int id)
{
    if(id < 1 || id > static_cast<int>(m_customLevelList.size())) return "";
    return m_customLevelList[id-1];
}

const std::vector<std::string>& CScreenLevelList::GetCustomLevelList()
{
    return m_customLevelList;
}

} // namespace Ui
