/*
 * pawssetupwindow.cpp - Author: Ian Donderwinkel
 *
 * Copyright (C) 2004 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
 *
 *
 * 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 (version 2 of the License)
 * 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, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
//////////////////////////////////////////////////////////////////////


#include <psconfig.h>
#include "globals.h"

#include <iutil/objreg.h>
#include <iutil/plugin.h>
#include <iutil/vfs.h>
#include <imap/loader.h>
#include <ivaria/conout.h>

#include <iengine/engine.h>
#include <iengine/region.h>
#include <iutil/cache.h>
#include <csutil/cfgfile.h>
#include <csutil/cspmeter.h>
#include <ivaria/sequence.h>
#include <iutil/stringarray.h>
#include <iutil/databuff.h>
// PAWS INCLUDES
#include "pawssetupwindow.h"
#include "paws/pawsmanager.h"
#include "paws/pawsradio.h"
#include "paws/pawscheckbox.h"
#include "paws/pawstextbox.h"
#include "paws/pawscrollbar.h"
#include "paws/pawsprogressbar.h"
#include "paws/pawslistbox.h"

#define PS_QUERY_PLUGIN(myref,intf, str)                          \
myref = CS_QUERY_REGISTRY (object_reg, intf);                      \
if (!myref) {                                                     \
    printf ("No " str " plugin!");\
    return false;\
}


// Customized progress bar

SCF_IMPLEMENT_IBASE (pawsRelightProgress)
  SCF_IMPLEMENTS_INTERFACE (iProgressMeter)
SCF_IMPLEMENT_IBASE_END

pawsRelightProgress::pawsRelightProgress (pawsTextBox* file,const char* map, pawsProgressBar* bar,int n)
        : granularity(10), tick_scale(2),
          total(n), current(0), anchor(0)
{
    this->bar = bar;
    bar->SetTotalValue(100.0f);

    real_current = 0;
    this->map = map; 
    this->file = file;
    SCF_CONSTRUCT_IBASE (0);
}

pawsRelightProgress::~pawsRelightProgress()
{
    SCF_DESTRUCT_IBASE ();
}

void RefreshDraw()
{
    PawsManager::GetSingleton().GetGraphics3D()->BeginDraw (CSDRAW_2DGRAPHICS);
    PawsManager::GetSingleton().Draw (); 
    PawsManager::GetSingleton().GetGraphics3D()->FinishDraw ();
    PawsManager::GetSingleton().GetGraphics2D()->Print (0);
}

void pawsRelightProgress::Step(unsigned int n)
{
    real_current += n;
    csString format;
    format.Format("%s (%d)",map.GetData(),real_current);
    file->SetText(format);

    if(current < total)
    {
        current += n;
        int const units = (current == total ? 100 :
            (((100 * current) / total) / granularity) * granularity);

        bar->SetCurrentValue(units);
        RefreshDraw();
    }
}

void pawsRelightProgress::Restart()
{
    Reset();
    bar->SetCurrentValue(0.0f);
    RefreshDraw();
}

void pawsRelightProgress::Abort ()
{
    current = total;
    bar->SetCurrentValue(100.0f);
    RefreshDraw();
}

void pawsRelightProgress::Finalize ()
{
    current = total;
    bar->SetCurrentValue(100.0f);
    RefreshDraw();
}

void pawsRelightProgress::SetGranularity(int n)
{
    if (n >= 1 && n <= 100)
        granularity = n;
}

void pawsRelightProgress::SetTickScale(int n)
{
    if (n >= 1 && n <= 100)
        tick_scale = n;
}

pawsSetupWindow::pawsSetupWindow()
{
    AdvancedMode(false);
    customWidth  = 1280;
    customHeight = 960;
}

bool pawsSetupWindow::PostSetup()
{
    csRef<iVFS> vfs = CS_QUERY_REGISTRY (PawsManager::GetSingleton().GetObjectRegistry(), iVFS);
   
    configClient = new csConfigFile("/planeshift/psclient.cfg", vfs);
    configGlobal = new csConfigFile("/planeshift/planeshift.cfg", vfs);

    rbgResolution= (pawsRadioButtonGroup*)FindWidget("resbox");
    rbgDepth     = (pawsRadioButtonGroup*)FindWidget("depthbox");
    rbgVBO       = (pawsRadioButtonGroup*)FindWidget("vbobox");

    cbSound        = (pawsCheckBox*)FindWidget("sound");
    cbFullScreen   = (pawsCheckBox*)FindWidget("fullscreen");
    cbAllMaps      = (pawsCheckBox*)FindWidget("allmaps");
    cbMultiQuality = (pawsCheckBox*)FindWidget("ms_quality");
    cbKeepMaps     = (pawsCheckBox*)FindWidget("keepmaps");
 
    btnOK        = (pawsButton*)FindWidget("OKButton");
    btnCancel    = (pawsButton*)FindWidget("CancelButton");
    btnLaunch    = (pawsButton*)FindWidget("LaunchButton");
    btnRelightMaps = (pawsButton*)FindWidget("relightMaps");
    btnRelightMap  = (pawsButton*)FindWidget("relightMap");
    btnShowRelight = (pawsButton*)FindWidget("showRelight");

    edtCustomWidth   = (pawsEditTextBox*)FindWidget("screenwidth");
    edtCustomHeight  = (pawsEditTextBox*)FindWidget("screenheight");
    edtStencil       = (pawsEditTextBox*)FindWidget("stencil");

    lblStreamBuffer = (pawsTextBox*)FindWidget("streambufferlbl");
    lblAnisotropy   = (pawsTextBox*)FindWidget("at");
    lblMultiSampling= (pawsTextBox*)FindWidget("ms");
    lblTextureSample= (pawsTextBox*)FindWidget("texdownc");
    lblRelightTarget= (pawsTextBox*)FindWidget("relightTarget");
    lblFontScale    = (pawsTextBox*)FindWidget("fontscalepct");

    scbAnisotropy   = (pawsScrollBar*)FindWidget("ani");
    scbMultiSampling= (pawsScrollBar*)FindWidget("multi");
    scbTextureSample= (pawsScrollBar*)FindWidget("texdown");
    scbFontScale    = (pawsScrollBar*)FindWidget("fontscale");

    relightProgress = (pawsProgressBar*)FindWidget("relightProgress");

    lsbMapFiles     = (pawsListBox*)FindWidget("maps");

    // Enum all maps
    iObjectRegistry* object_reg = setupApp->GetObjReg();
    PS_QUERY_PLUGIN (engine, iEngine, "iEngine");
    PS_QUERY_PLUGIN (vfs,    iVFS,    "iVFS");
    csRef<iStringArray> maps = vfs->FindFiles("/planeshift/art/world/");

    csArray<csString> files; // Bug in VFS making stuff appear twice
    for(size_t i = 0; i < maps->Length();i++)
    {
        csString map = maps->Get(i);
        
        // Zip?
        if(map.Slice(map.Length()-3,3) != "zip")
            continue;

        size_t start = map.FindLast('/')+1;
        map = map.Slice(start,map.Length() - start);
        map = map.Slice(0,map.Length()-4);

        // Inserted this?
        bool found = false;
        for(size_t z = 0; z < files.Length();z++)
            if(files[z] == map)
                found = true;

        if(found)
            break; // Second walk, break

        // Insert it
        pawsListBoxRow* row = lsbMapFiles->NewRow();
        pawsTextBox* box = (pawsTextBox*)row->GetColumn(0);
        box->SetText(map);

        files.Push(map);
    }


    LoadSettings();
    return true;
}

bool pawsSetupWindow::OnButtonPressed( int mouseButton, int keyModifier, pawsWidget* widget )
{
    if (widget==btnOK)
    {
        SaveSettings();
        setupApp->Quit();
    }
    else if (widget==btnCancel)
    {
        setupApp->Quit();
    }
    else if (widget==btnLaunch)
    {
        SaveSettings();
    #ifdef CS_PLATFORM_WIN32
        // Lanuch PlaneShift in the win way
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        ZeroMemory( &si, sizeof(si) );
        si.cb = sizeof(si);
        ZeroMemory( &pi, sizeof(pi) );

        // Start the second updater process. 
        CreateProcess( NULL,    // No module name (use command line). 
            "psclient.exe",     // Command line.
            NULL,               // Process handle not inheritable. 
            NULL,               // Thread handle not inheritable. 
            FALSE,              // Set handle inheritance to FALSE. 
            CREATE_NEW_CONSOLE, // Give the process it's own console
            NULL,               // Use parent's environment block. 
            NULL,               // Use parent's starting directory. 
            &si,                // Pointer to STARTUPINFO structure.
            &pi );              // Pointer to PROCESS_INFORMATION structure.

    #elif defined(CS_PLATFORM_UNIX)
        // *NIX have a quite easier way to do that :)
        system("./psclient &");
    #elif defined(CS_PLATFORM_MACOSX) //is this the right define?
        //system("open ./psclient.app &");
    #else
    #error "Bad platform for launch"
    #endif
        setupApp->Quit();
    }
    else if(widget == cbAllMaps && cbAllMaps->GetState())
    {
        PawsManager::GetSingleton().CreateWarningBox("Warning! While this will eliminate all loading while ingame, this will demand much more RAM and the first loading will take longer to load");
    }
    else if(widget == cbKeepMaps && cbKeepMaps->GetState())
    {
        PawsManager::GetSingleton().CreateWarningBox("This experimental mode will only load zones as you enter them, but will keep them loaded. It does not unload zones yet.");
    }
    else if(!strcmp(widget->GetName(),"Skins"))
    {
        pawsWidget* skin = PawsManager::GetSingleton().FindWidget("skin");
        if(skin)
        {
            skin->SetFade(false);
            skin->SetMaxAlpha(1);
            skin->Show();
        }
    }
    else if(widget == btnRelightMap)
    {
        csString file;
        if ( lsbMapFiles->GetSelected() )
        {
            pawsTextBox* txt = (pawsTextBox*)lsbMapFiles->GetSelected()->GetColumn(0);
            file.Format("/planeshift/art/world/%s.zip",txt->GetText());
            RelightMap(file);
        }
    }
    else if(widget == btnRelightMaps)
    {
        RelightMaps();
    }
    else if(widget == btnShowRelight)
    {
        btnShowRelight->Hide();
        relightProgress->Show();
        lsbMapFiles->Show();
        btnRelightMap->Show();
        if(advanced)
            btnRelightMaps->Show();
        lblRelightTarget->Show();
    }

  return false;
}


void pawsSetupWindow::LoadSettings()
{
    char buf[256];

    // video settings
    int      width  = configGlobal->GetInt("Video.ScreenWidth");
    int      height = configGlobal->GetInt("Video.ScreenHeight");
    
    sprintf(buf,"%ix%i",width,height);
    csString res(buf);

    if (!rbgResolution->SetActive(buf))
    {
        rbgResolution->SetActive("rescustom");
        customWidth  = width;
        customHeight = height;
    }

    sprintf(buf,"%i",customWidth);
    edtCustomWidth->SetText(buf);
    sprintf(buf,"%i",customHeight);
    edtCustomHeight->SetText(buf);

    cbFullScreen->SetState(configGlobal->GetBool("Video.Fullscreen"));
    rbgDepth->SetActive(configGlobal->GetStr("Video.ScreenDepth"));
    
    // Stencil
    int stencil = configClient->GetInt("Video.OpenGL.StencilThreshold",50);
    csString stenstr;
    stenstr.Format("%d",stencil);
    edtStencil->SetText(stenstr);

    // VBO
    if(!configClient->KeyExists("Video.OpenGL.UseExtension.GL_ARB_vertex_buffer_object"))
    {
        rbgVBO->SetActive("def");
    }
    else
    {
        bool value = configClient->GetBool("Video.OpenGL.UseExtension.GL_ARB_vertex_buffer_object",true);
        if(value)
            rbgVBO->SetActive("on");
        else
            rbgVBO->SetActive("off");
    }

    // sound
    cbSound->SetState(configClient->KeyExists("System.PlugIns.iSndSysRenderer"));

    // all maps
    cbAllMaps->SetState(configClient->GetBool("Planeshift.Client.Loading.AllMaps",false));
    // keep maps
    cbKeepMaps->SetState(configClient->GetBool("Planeshift.Client.Loading.KeepMaps",false));

    // Multisampling quality
    cbMultiQuality->SetState(configClient->GetBool("Video.OpenGL.MultisampleFavorQuality",true));

    // Anisotropy
    float aniso = configClient->GetFloat("Video.OpenGL.TextureFilterAnisotropy",1.0f);
    scbAnisotropy->SetMinValue(1.0f);
    scbAnisotropy->SetMaxValue(16.0f);
    scbAnisotropy->SetTickValue(0.5f);
    scbAnisotropy->SetCurrentValue(aniso,true);

    // Multisampling
    int multi = configClient->GetInt("Video.OpenGL.MultiSamples",0);
    scbMultiSampling->SetMaxValue(8.0f);
    scbMultiSampling->SetTickValue(0.0f); // handled extra in onscroll
    scbMultiSampling->SetCurrentValue((float)multi,true);

    // Texture downsampling
    int samp = configClient->GetInt("Video.OpenGL.TextureDownsample",0);
    scbTextureSample->SetMaxValue(4.0f);
    scbTextureSample->SetTickValue(1.0f); // handled extra in onscroll
    scbTextureSample->SetCurrentValue((float)samp,true);

    // Font scaling
    int fontFactor = configClient->GetInt("Font.ScalePercent",100);
    scbFontScale->SetMinValue(50.0f);
    scbFontScale->SetMaxValue(150.0f);
    scbFontScale->SetTickValue(5.0f); // handled extra in onscroll
    scbFontScale->SetCurrentValue((float)fontFactor,true);
}

bool pawsSetupWindow::OnScroll(int dir,pawsScrollBar* widget)
{
    if(widget == scbAnisotropy)
    {
        // Easy, just update the label
        csString lbl;
        lbl.Format("%.1f",widget->GetCurrentValue());
        lblAnisotropy->SetText(lbl);
    }
    else if(widget == scbMultiSampling)
    {
        // Harder, needs to be 1,2,4,8,16,32
        int value = (int)widget->GetCurrentValue();
        int pow = 0;

        while(value > 0)
        {
            value = value >> 1;
            pow++;
        }
        pow--;

        if(pow >= 0)
            value = 1 << pow;
        else
            value = 0;

        // Note: direction seems to be reversed while horz
        if(value == 0 && dir == SCROLL_DOWN)
            value = 1;
        else if(dir != SCROLL_SET)
        {
            if(dir == SCROLL_UP)
                value = value >> 1;
            else
                value = value << 1;
        }

        if(value > (int)widget->GetMaxValue())
        {
            value = (int)widget->GetMaxValue();
        }

        widget->SetCurrentValue(value,false);

        // Update label
        csString lbl;
        lbl.Format("%d",value);
        lblMultiSampling->SetText(lbl);
    }
    else if(widget == scbTextureSample)
    {
        int pow = (int)widget->GetCurrentValue();
        int value = 1 << pow;

        csString lbl;
        lbl.Format("1/%d",value);
        lblTextureSample->SetText(lbl);
    }
    else if(widget == scbFontScale)
    {
        int value = (int)widget->GetCurrentValue();

        csString lbl;
        lbl.Format("%d%%",value);
        lblFontScale->SetText(lbl);
    }
    else
        return false; // Unkown widget

    return true;
}

bool pawsSetupWindow::RelightMaps()
{
    iObjectRegistry* object_reg = setupApp->GetObjReg();
    PS_QUERY_PLUGIN (vfs,    iVFS,    "iVFS");
    
    // Get all maps
    csRef<iStringArray> files = vfs->FindFiles("/planeshift/art/world/");
    csArray<csString> ok;
    csArray<csString> failed;

    for(size_t i = 0; i < files->Length();i++)
    {
        csString file,ext;
        file = files->Get(i);

        size_t start = file.FindLast('.');
        if( start != -1)        
            ext = file.Slice(start,file.Length() - start);
        
        if(ext != ".zip")
            continue; // Not a map

        // load and relight
        RelightMap(file);
    }

    return true;
}

bool pawsSetupWindow::RelightMap(const char* zip)
{
    iObjectRegistry* object_reg = setupApp->GetObjReg();
    PS_QUERY_PLUGIN (vfs,    iVFS,    "iVFS");

    static csArray<csString> litt;
    // Litt this already?
    for(size_t i = 0; i < litt.Length();i++)
        if(litt[i] == zip)
            return true; // Already done, so return true

    printf("Relightning map %s\n",zip);

    // Load the loader plugin
    if(!loader.IsValid())
    {
        csRef<iPluginManager> pluginMgr;
        PS_QUERY_PLUGIN(pluginMgr,iPluginManager,"iPluginManager");
        
        if(!loader)
        {
            loader = CS_LOAD_PLUGIN(pluginMgr,"crystalspace.level.loader",iLoader);
            if(!loader)
            {
                printf("Couldn't load the loader plugin! Can't relight maps!\n");
                return false;
            }
        }
    }

    // Load the map if it exists
    if(!vfs->Exists(zip))
        return false;

    vfs->PushDir();
    lblRelightTarget->SetText("Loading map..");
    RefreshDraw();

    // Reset lighnining
    engine->SetCacheManager(NULL);
    engine->SetLightingCacheMode(CS_ENGINE_CACHE_WRITE); // This will force a relightning and write the result to the zip
    engine->ResetWorldSpecificSettings();

    csRef<iDataBuffer> real = vfs->GetRealPath(zip);
    vfs->Mount("/lev/relightlvl",real->GetData());
    SetMapDir("relightlvl");

    // Recreate the cache manager
    engine->GetCacheManager();

    // Create the region and clear it if it existed before
    iRegion* region = engine->CreateRegion("relight_level");
    region->DeleteAll();

    // load the map
    if(!loader->LoadMapFile("world",false,region,false,true))
        return false;

    // Update the target label, it will be redrawn when the progress bar advances
    csString smallZip = zip;
    size_t start = smallZip.FindLast('/')+1;
    smallZip = smallZip.Slice(start,smallZip.Length() - start);
    smallZip = smallZip.Slice(0,smallZip.Length()-4); // Remove .zip  

    // Create a progress meter
    pawsRelightProgress* meter = new pawsRelightProgress(lblRelightTarget,smallZip,relightProgress);
    region->PrepareTextures();
    engine->Prepare(meter); // relight
    engine->GetCacheManager()->Flush();
    vfs->Sync();

    delete meter;

    // Remove and unmount
    region->DeleteAll();
    vfs->Unmount("/lev/relightlvl",real->GetData());
    vfs->PopDir();

    // If this is the last, nothing will overwrite this message
    lblRelightTarget->SetText("Relightning done");
    litt.Push(zip);
    return true;
}

bool pawsSetupWindow::SetMapDir (const char* map_dir)
{
    csStringArray paths;
    paths.Push ("/lev/");
    if (!vfs->ChDirAuto (map_dir, &paths, 0, "world"))
    {
        printf("Error setting directory '%s'!", map_dir);
        return false;
    }
    return true;
}

void pawsSetupWindow::SaveSettings()
{
    configGlobal->SetBool("Video.Fullscreen",cbFullScreen->GetState());
    configGlobal->SetStr("Video.ScreenDepth",rbgDepth->GetActive());

    configClient->SetInt("Video.OpenGL.StencilThreshold",atoi(edtStencil->GetText()));

    // VBO
    int value;
    csString vbo = rbgVBO->GetActive();
    if(vbo == "def")
        value = 2;
    else if(vbo == "on")
        value = 1;
    else
        value = 0;

    if(value==2)
        configClient->DeleteKey("Video.OpenGL.UseExtension.GL_ARB_vertex_buffer_object");
    else
        configClient->SetBool("Video.OpenGL.UseExtension.GL_ARB_vertex_buffer_object",(value ? true : false));

    // resolution
    int width, height;
    csString res = rbgResolution->GetActive();
    if (res=="800x600")
    {
        width=800;
        height=600;
    }
    else if (res=="1024x768")
    {
        width=1024;
        height=768;
    }
    else
    {
        width=atoi(edtCustomWidth->GetText());
        height=atoi(edtCustomHeight->GetText());
    }       
    configGlobal->SetInt("Video.ScreenWidth",width);
    configGlobal->SetInt("Video.ScreenHeight",height);

    // sound enabled/disabled
    if (cbSound->GetState())
    {
        configClient->SetStr("System.PlugIns.iSndSysRenderer","crystalspace.sndsys.renderer.software");
    }
    else
    {
        configClient->DeleteKey("System.PlugIns.iSndSysRenderer");
    }

    // all maps
    configClient->SetBool("Planeshift.Client.Loading.AllMaps",cbAllMaps->GetState());
    // keep maps
    configClient->SetBool("Planeshift.Client.Loading.KeepMaps",cbKeepMaps->GetState());

    configClient->SetBool ("Video.OpenGL.MultisampleFavorQuality"   ,cbMultiQuality->GetState());
    configClient->SetInt  ("Video.OpenGL.MultiSamples"              ,(int)scbMultiSampling->GetCurrentValue());
    configClient->SetFloat("Video.OpenGL.TextureFilterAnisotropy"   ,scbAnisotropy->GetCurrentValue());
    configClient->SetInt  ("Video.OpenGL.TextureDownsample"         ,(int)scbTextureSample->GetCurrentValue());

    configClient->SetInt("Font.ScalePercent", (int)scbFontScale->GetCurrentValue() );

    configGlobal->Save();
    configClient->Save();
}
