//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
// RCS-ID:      $Id$
// Copyright (C) 2002 - 2010, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// ctlDefaultSecurityPanel.cpp - Panel with default security information
//
//////////////////////////////////////////////////////////////////////////


// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/imaglist.h>

// App headers
#include "pgAdmin3.h"
#include "utils/sysLogger.h"
#include "ctl/ctlDefaultSecurityPanel.h"
#include "db/pgConn.h"
#include "schema/pgObject.h"
#include "schema/pgGroup.h"
#include "schema/pgUser.h"



BEGIN_EVENT_TABLE(ctlDefaultSecurityPanel, wxPanel)
    EVT_LIST_ITEM_SELECTED(CTL_DEFLBPRIV,  ctlDefaultSecurityPanel::OnPrivSelChange)
    EVT_BUTTON(CTL_DEFADDPRIV,             ctlDefaultSecurityPanel::OnAddPriv)
    EVT_BUTTON(CTL_DEFDELPRIV,             ctlDefaultSecurityPanel::OnDelPriv)
    EVT_TEXT(CTL_DEFCBGROUP,               ctlDefaultSecurityPanel::OnGroupChange)
    EVT_COMBOBOX(CTL_DEFCBGROUP,           ctlDefaultSecurityPanel::OnGroupChange)
    EVT_CHECKBOX(CTL_DEFALLPRIV,           ctlDefaultSecurityPanel::OnPrivCheckAll)
    EVT_CHECKBOX(CTL_DEFALLPRIVGRANT,      ctlDefaultSecurityPanel::OnPrivCheckAllGrant)
    EVT_CHECKBOX(CTL_DEFPRIVCB,            ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+2,          ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+4,          ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+6,          ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+8,          ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+10,         ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+12,         ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+14,         ctlDefaultSecurityPanel::OnPrivCheck)
    EVT_CHECKBOX(CTL_DEFPRIVCB+16,         ctlDefaultSecurityPanel::OnPrivCheck)
END_EVENT_TABLE();

DEFINE_EVENT_TYPE(EVT_DEFAULTSECURITYPANEL_CHANGE)

ctlDefaultSecurityPanel::ctlDefaultSecurityPanel(wxNotebook *nb, const wxString &privList, const char *privChars, wxImageList *imgList)
: wxPanel(nb, -1, wxDefaultPosition, wxDefaultSize)
{
    nbNotebook = nb;
    nbNotebook->AddPage(this, _("Default Privileges"));

    connection = 0;
    privilegeChars=privChars;
    allPrivileges=0;

    privCheckboxes=0;

    wxStringTokenizer privileges(privList, wxT(","));
    privilegeCount=privileges.CountTokens();
    
    wxFlexGridSizer *item0 = new wxFlexGridSizer(3, 1, 5, 5);
    item0->AddGrowableCol(0);
    item0->AddGrowableRow(0);

    if (privilegeCount)
    {
        bool needAll=(privilegeCount > 1);
        privCheckboxes = new wxCheckBox*[privilegeCount*2];
        int i=0;

        wxFlexGridSizer *itemSizer1 = new wxFlexGridSizer(1, 1, 5, 5);
        itemSizer1->AddGrowableCol(0);
        itemSizer1->AddGrowableRow(0);
        lbPrivileges = new ctlListView(this, CTL_DEFLBPRIV, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER|wxLC_REPORT);
        lbPrivileges->SetImageList(imgList, wxIMAGE_LIST_SMALL);
        lbPrivileges->AddColumn(_("User/Group"), 35, wxLIST_FORMAT_LEFT);
        lbPrivileges->AddColumn(_("Schema"), 35, wxLIST_FORMAT_LEFT);
        lbPrivileges->AddColumn(_("Type"), 35, wxLIST_FORMAT_LEFT);
        lbPrivileges->AddColumn(_("Privileges"), 35, wxLIST_FORMAT_LEFT);
        itemSizer1->Add(lbPrivileges, 0, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT, 4);
        item0->Add(itemSizer1, 0, wxEXPAND|wxALL, 5);

        wxBoxSizer* itemSizer2 = new wxBoxSizer(wxHORIZONTAL);
        btnAddPriv = new wxButton(this, CTL_DEFADDPRIV, _("Add/Change"));
        itemSizer2->Add(btnAddPriv, 0, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT, 4);
        btnDelPriv = new wxButton(this, CTL_DEFDELPRIV, _("Remove"));
        itemSizer2->Add(btnDelPriv, 0, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT, 4);
        item0->Add(itemSizer2, 0, wxEXPAND|wxALL, 0);

        wxStaticBox* sb = new wxStaticBox(this, -1, _("Privileges"));
        wxBoxSizer* itemSizer3 = new wxStaticBoxSizer( sb, wxVERTICAL );
        item0->Add(itemSizer3, 0, wxEXPAND|wxALL, 5);

        wxBoxSizer* itemSizer4a = new wxBoxSizer(wxHORIZONTAL);
        stGroup = new wxStaticText(this, CTL_DEFSTATICGROUP, _("Group"));
        itemSizer4a->Add(stGroup, 0, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT, 4);
        cbGroups = new ctlComboBox(this, CTL_DEFCBGROUP, wxDefaultPosition, wxDefaultSize);
        cbGroups->Append(wxT("public"));
        cbGroups->SetSelection(0);
        itemSizer4a->Add(cbGroups, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT);
        itemSizer3->Add(itemSizer4a, 0, wxEXPAND|wxALL, 0);

        wxBoxSizer* itemSizer4b = new wxBoxSizer(wxHORIZONTAL);
        stSchema = new wxStaticText(this, CTL_DEFSTATICGROUP, _("Schema"));
        itemSizer4b->Add(stSchema, 0, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT, 4);
        cbSchemas = new ctlComboBox(this, CTL_DEFCBGROUP, wxDefaultPosition, wxDefaultSize);
        itemSizer4b->Add(cbSchemas, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT);
        itemSizer3->Add(itemSizer4b, 0, wxEXPAND|wxALL, 0);

        wxBoxSizer* itemSizer4c = new wxBoxSizer(wxHORIZONTAL);
        stType = new wxStaticText(this, CTL_DEFSTATICGROUP, _("Type"));
        itemSizer4c->Add(stType, 0, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT, 4);
        cbTypes = new ctlComboBox(this, CTL_DEFCBGROUP, wxDefaultPosition, wxDefaultSize);
        cbTypes->Append(wxT("Tables"));
        cbTypes->Append(wxT("Views"));
        cbTypes->Append(wxT("Sequences"));
        cbTypes->Append(wxT("Functions"));
        cbTypes->SetSelection(0);
        itemSizer4c->Add(cbTypes, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT);
        itemSizer3->Add(itemSizer4c, 0, wxEXPAND|wxALL, 0);

        /* border size depends on the plateform */
        #ifdef __WXMSW__
        int bordersize = 4;
        #endif
        #ifdef __WXMAC__
        int bordersize = 3;
        #endif
        #ifdef __WXGTK__
        int bordersize = 0;
        #endif

        if (needAll)
        {
            wxBoxSizer* itemSizer5 = new wxBoxSizer(wxHORIZONTAL);
            allPrivileges = new wxCheckBox(this, CTL_DEFALLPRIV, wxT("ALL"));
            itemSizer5->Add(allPrivileges, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT);
            allPrivilegesGrant = new wxCheckBox(this, CTL_DEFALLPRIVGRANT, wxT("WITH GRANT OPTION"));
            itemSizer5->Add(allPrivilegesGrant, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT);
            allPrivilegesGrant->Disable();
            itemSizer3->Add(itemSizer5, 0, wxALL, bordersize);
        }

        while (privileges.HasMoreTokens())
        {
            wxString priv=privileges.GetNextToken();
            wxCheckBox *cb;
            wxBoxSizer* itemSizer6 = new wxBoxSizer(wxHORIZONTAL);
            cb=new wxCheckBox(this, CTL_DEFPRIVCB+i, priv);
            itemSizer6->Add(cb, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT);
            privCheckboxes[i++] = cb;
            cb=new wxCheckBox(this, CTL_DEFPRIVCB+i, wxT("WITH GRANT OPTION"));
            itemSizer6->Add(cb, wxEXPAND|wxALIGN_CENTRE_VERTICAL|wxTOP|wxLEFT|wxRIGHT);
            cb->Disable();
            privCheckboxes[i++] = cb;
            itemSizer3->Add(itemSizer6, 0, wxALL, bordersize);
        }
    }
    
    this->SetSizer(item0);
    item0->Fit(this);
}


ctlDefaultSecurityPanel::~ctlDefaultSecurityPanel()
{
    if (privCheckboxes)
        delete[] privCheckboxes;
}


void ctlDefaultSecurityPanel::SetConnection(pgConn *conn)
{
    connection=conn;
    if (connection && stGroup && connection->BackendMinimumVersion(8, 1))
        stGroup->SetLabel(_("Role"));
    if (connection)
    {
		pgSet *set=connection->ExecuteSet(wxT("SELECT nspname FROM pg_namespace ORDER BY 1"));
		if (set)
		{
		    while (!set->Eof())
		    {
		        cbSchemas->Append(set->GetVal(0));
		        set->MoveNext();
		    }
		}
    }
}


wxString ctlDefaultSecurityPanel::GetGrant(const wxString &allPattern, const wxString &grantObject, wxArrayString *currentAcl, wxString column)
{
    wxArrayString tmpAcl;
    if (currentAcl)
        tmpAcl = *currentAcl;

    wxString sql;
    int cnt=lbPrivileges->GetItemCount();
    int pos;
    unsigned int i;

    for (pos=0 ; pos < cnt ; pos++)
    {
        wxString name=lbPrivileges->GetText(pos);
        wxString value=lbPrivileges->GetText(pos, 1);

        int nameLen=name.Length();

        bool privWasAssigned=false;
        bool privPartiallyAssigned=false;
        for (i=0 ; i < tmpAcl.GetCount() ; i++)
        {
            if (tmpAcl.Item(i).Left(nameLen) == name)
            {
                privPartiallyAssigned=true;
                if (tmpAcl.Item(i).Mid(nameLen+1) == value)
                    privWasAssigned=true;
                tmpAcl.RemoveAt(i);
                break;
            }
        }

        if (name.Left(6).IsSameAs(wxT("group "), false))
            name = wxT("GROUP ") + qtIdent(name.Mid(6));
        else
            name=qtIdent(name);

        if (!privWasAssigned)
        {
            if (privPartiallyAssigned)
                sql += pgObject::GetPrivileges(allPattern, wxT(""), grantObject, name, column);
            sql += pgObject::GetPrivileges(allPattern, value, grantObject, name, column);
        }
    }

    for (i=0 ; i < tmpAcl.GetCount() ; i++)
    {
        wxString name=tmpAcl.Item(i).BeforeLast('=');

        if (name.Left(6).IsSameAs(wxT("group "), false))
            name = wxT("GROUP ") + qtIdent(name.Mid(6));
        else
            name=qtIdent(name);
        sql += pgObject::GetPrivileges(allPattern, wxT(""), grantObject, name, column);
    }
    return sql;
}


void ctlDefaultSecurityPanel::OnGroupChange(wxCommandEvent &ev)
{
    cbGroups->GuessSelection(ev);
    wxString name=cbGroups->GetGuessedStringSelection();

    btnAddPriv->Enable(!name.Strip(wxString::both).IsEmpty());
}

    
void ctlDefaultSecurityPanel::OnPrivCheckAll(wxCommandEvent& ev)
{
    bool all=allPrivileges->GetValue();
    int i;
    for (i=0 ; i < privilegeCount ; i++)
    {
        if (all)
        {
            // We use the Name property of the checkboxes as a flag to note if that
            // box should remain disabled (eg. CONNECT for a DB on PG < 8.2
            if (privCheckboxes[i*2]->GetName() != wxT("DISABLE"))
            {
                privCheckboxes[i*2]->SetValue(true);
                privCheckboxes[i*2]->Disable();
                privCheckboxes[i*2+1]->Disable();
                allPrivilegesGrant->Enable(GrantAllowed());
            }
        }
        else
        {
            if (privCheckboxes[i*2]->GetName() != wxT("DISABLE"))
            {
                allPrivilegesGrant->Disable();
                allPrivilegesGrant->SetValue(false);
                privCheckboxes[i*2]->Enable();
                CheckGrantOpt(i);
            }
        }
    }
}



void ctlDefaultSecurityPanel::OnPrivCheckAllGrant(wxCommandEvent& ev)
{
    bool grant=allPrivilegesGrant->GetValue();
    int i;
    for (i=0 ; i < privilegeCount ; i++)
        privCheckboxes[i*2+1]->SetValue(grant);
}


void ctlDefaultSecurityPanel::OnPrivCheck(wxCommandEvent& ev)
{
    int id=(ev.GetId()-CTL_DEFPRIVCB) /2;
    CheckGrantOpt(id);
}


void ctlDefaultSecurityPanel::CheckGrantOpt(int id)
{
    bool canGrant=(GrantAllowed() && privCheckboxes[id*2]->GetValue());
    if (canGrant)
        privCheckboxes[id*2+1]->Enable();
    else
    {
        privCheckboxes[id*2+1]->SetValue(false);
        privCheckboxes[id*2+1]->Disable();
    }
}


void ctlDefaultSecurityPanel::OnDelPriv(wxCommandEvent &ev)
{
    if (lbPrivileges->GetFirstSelected() == -1)
        return;
        
    lbPrivileges->DeleteCurrentItem();
    
    wxCommandEvent event( EVT_DEFAULTSECURITYPANEL_CHANGE, GetId() );
    event.SetEventObject( this );
    GetEventHandler()->ProcessEvent( event );

    ev.Skip();
}


void ctlDefaultSecurityPanel::OnAddPriv(wxCommandEvent &ev)
{
    wxString name=cbGroups->GetGuessedStringSelection();

    long pos=lbPrivileges->FindItem(-1, name);
    if (pos < 0)
    {
        pos = lbPrivileges->GetItemCount();
        int icon=userFactory.GetIconId();

        if (name.Left(6).IsSameAs(wxT("group "), false))
            icon = groupFactory.GetIconId();
        else if (name.IsSameAs(wxT("public"), false))
            icon = PGICON_PUBLIC;

        lbPrivileges->InsertItem(pos, name, icon);
    }
    wxString value;
    int i;
    for (i=0 ; i < privilegeCount ; i++)
    {
        if (privCheckboxes[i*2]->GetValue())
        {
            value += privilegeChars[i];
            if (privCheckboxes[i*2+1]->GetValue())
                value += '*';
        }
    }
    lbPrivileges->SetItem(pos, 1, value);

    wxCommandEvent event( EVT_DEFAULTSECURITYPANEL_CHANGE, GetId() );
    event.SetEventObject( this );
    GetEventHandler()->ProcessEvent( event );

    ev.Skip();
}


void ctlDefaultSecurityPanel::OnPrivSelChange(wxListEvent &ev)
{
    if (!cbGroups)
        return;
    if (allPrivileges)
    {
        allPrivileges->SetValue(false);
        allPrivilegesGrant->SetValue(false);
        allPrivilegesGrant->Disable();
    }
    long pos=lbPrivileges->GetSelection();
    if (pos >= 0)
    {
        wxString name=lbPrivileges->GetText(pos);
        wxString value=lbPrivileges->GetText(pos, 1);

        pos=cbGroups->FindString(name);
        if (pos < 0)
        {
            cbGroups->Append(name);
            pos=cbGroups->GetCount()-1;
        }
        cbGroups->SetSelection(pos);

        int i;
        for (i=0 ; i < privilegeCount ; i++)
        {
            if (privCheckboxes[i*2]->GetName() != wxT("DISABLE"))
            {
                privCheckboxes[i*2]->Enable();
                int index=value.Find(privilegeChars[i]);
                if (index >= 0)
                {
                    privCheckboxes[i*2]->SetValue(true);
                    privCheckboxes[i*2+1]->SetValue(value.Mid(index+1, 1) == wxT("*"));
                }
                else
                    privCheckboxes[i*2]->SetValue(false);
            }
            CheckGrantOpt(i);
        }
    }
    btnAddPriv->Enable();
}



bool ctlDefaultSecurityPanel::GrantAllowed() const
{
    if (!connection->BackendMinimumVersion(7, 4))
        return false;

    wxString user=cbGroups->GetValue();
    if (user.IsSameAs(wxT("public"), false))
        return false;

    if (!connection->BackendMinimumVersion(8, 1) &&
        user.Left(6).IsSameAs(wxT("group "), false))
        return false;

    return true;
}

bool ctlDefaultSecurityPanel::DisablePrivilege(const wxString &priv)
{
    for (int i=0; i < privilegeCount; i++)
    {
        if (privCheckboxes[i*2]->GetLabel() == priv)
        {
            // Use the Name property as a flag so we don't accidently reenable the control later
            privCheckboxes[i*2]->SetName(wxT("DISABLE"));
            privCheckboxes[i*2]->Disable();
            return true;
        }
    }
    return false;
}
