/**************************************************************************

    This file is part of explorer95, a file manager for fvwm95.
    Copyright (C) 1996, 1997 David Barth, Hector Peraza.

    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 2 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, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <time.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <xclass/OXClient.h>
#include <xclass/OXWindow.h>
#include <xclass/OXFrame.h>
#include <xclass/OXWidgets.h>
#include <xclass/OXMsgBox.h>
#include <xclass/OXFileContainer.h>
#include <xclass/OString.h>
#include <xclass/OPicture.h>
#include <xclass/utils.h>

#include "OXNewClient.h"
#include "OXNewWidgets.h"
#include "OXProperties.h"


extern OXFileContainer *fileWindow;


//-------------------------------------------------------------------

OXPropertiesDialog::OXPropertiesDialog(const OXWindow *p, const OXWindow *main,
                    OString *filename, int type, int is_link,
                    unsigned long options) :
  OXTransientFrame(p, main, 100, 100, options) {
    OXCompositeFrame *tf;
    int ax, ay, width, height;
    Window wdummy;
    char name[PATH_MAX], wname[PATH_MAX];
    const OPicture *pic, *lpic;

    _filename = filename;
    _type = type;
    _is_link = is_link;
    strcpy(name, _filename->GetString());

    //======= [1] this frame will contain the "OK Cancel Apply" buttons:
    bframe = new OXHorizontalFrame(this, 60, 20, FIXED_WIDTH);

    //--- create the buttons
    Ok     = new OXTextButton(bframe, new OHotString("OK"), ID_OK);
    Cancel = new OXTextButton(bframe, new OHotString("Cancel"), ID_CANCEL);
    Apply  = new OXTextButton(bframe, new OHotString("&Apply"), ID_APPLY);

    //--- Apply is initially disabled
    Apply->SetState(BUTTON_DISABLED);

    //--- send button messages to this dialog
    Ok->Associate(this);
    Cancel->Associate(this);
    Apply->Associate(this);

    //--- layout for buttons: top align, equally expand horizontally
    bly = new OLayoutHints(LHINTS_TOP | LHINTS_EXPAND_X, 5, 0, 0, 0);

    //--- layout for the frame: place at bottom, some gap at the left
    bfly = new OLayoutHints(LHINTS_BOTTOM | LHINTS_RIGHT, 0, 5, 0, 4);

    bframe->AddFrame(Ok, bly);
    bframe->AddFrame(Cancel, bly);
    bframe->AddFrame(Apply, bly);

    width = Ok->GetDefaultWidth();   
    width = max(width, Cancel->GetDefaultWidth());
    width = max(width, Apply->GetDefaultWidth());
    bframe->Resize((width + 20) * 3, bframe->GetDefaultHeight());

    AddFrame(bframe, bfly);

    //====== [2] Tab widget

    tab = new OXTab(this, 320, 300);
    Ltab = new OLayoutHints(LHINTS_EXPAND_Y | LHINTS_EXPAND_X, 5, 5, 5, 5);

    //---- Tab 1: General

    tf = tab->AddTab(new OString("General"));

    vf1 = new OXVerticalFrame(tf, 10, 10);
    Vly = new OLayoutHints(LHINTS_EXPAND_X | LHINTS_EXPAND_Y, 10, 10, 10, 10);

    lhf1 = new OLayoutHints(LHINTS_EXPAND_X | LHINTS_TOP, 0, 0, 10, 0);
    vf1->AddFrame(hf1 = new OXHorizontalFrame(vf1, 10, 10), lhf1);

    fileWindow->GetFilePictures(&pic, &lpic, _type, _is_link, name, False);
    FileIcon = new OXFileIcon(hf1, pic, lpic,
                                   CHILD_FRAME, _defaultFrameBackground);

    //--- text buffer is owned by OXTextEntry, and will be deleted on OXTextEntry deletion!
    bfr1 = new OTextBuffer(256);
    FileName = new OXTextEntry(hf1, bfr1, 101);
    FileName->ChangeOptions(FileName->GetOptions() & 
              ~(RAISED_FRAME | SUNKEN_FRAME | DOUBLE_BORDER));
    FileName->SetBackgroundColor(_defaultFrameBackground);
//    FileName->Resize(200, GetDefaultHeight());
    FileName->Associate(this);

    Tly = new OLayoutHints(LHINTS_CENTER_Y | LHINTS_EXPAND_X, 10, 0, 0, 0);
    Ily = new OLayoutHints(LHINTS_CENTER_Y | LHINTS_LEFT, 5, 0, 0, 0);

    hf1->AddFrame(FileIcon, Ily);
    hf1->AddFrame(FileName, Tly);

    Lr = new OLayoutHints(LHINTS_EXPAND_X | LHINTS_TOP, 0, 0, 10, 10);
    vf1->AddFrame(ln1 = new OXHorizontal3dLine(vf1), Lr);

    //--- type, location, owner, group...

    Lc = new OLayoutHints(LHINTS_LEFT | LHINTS_TOP);
    vf1->AddFrame(d1 = new OX2ColumnsFrame(vf1, 10, 10), Lc);

    d1->AddFrame(ltype0 = new OXLabel(d1, new OString("Type:")), Lc);
    d1->AddFrame(ltype = new OXLabel(d1, new OString("Unknown")), Lc);
    d1->AddFrame(ldev0 = new OXLabel(d1, new OString("Location:")), Lc);
    d1->AddFrame(ldev = new OXLabel(d1, new OString("Unknown")), Lc);
    d1->AddFrame(lusr0 = new OXLabel(d1, new OString("Owner:")), Lc);
    d1->AddFrame(lusr = new OXLabel(d1, new OString("Unknown")), Lc);
    d1->AddFrame(lgrp0 = new OXLabel(d1, new OString("Group:")), Lc);
    d1->AddFrame(lgrp = new OXLabel(d1, new OString("Unknown")), Lc);
    d1->AddFrame(llnks0 = new OXLabel(d1, new OString("Links:")), Lc);
    d1->AddFrame(llnks = new OXLabel(d1, new OString("Unknown")), Lc);
    d1->AddFrame(lsz0 = new OXLabel(d1, new OString("Size:")), Lc);
    d1->AddFrame(lsz = new OXLabel(d1, new OString("Unknown")), Lc);
//    d1->AddFrame(new OXLabel(d1, new OString("Contents:")), Lc);

    vf1->AddFrame(ln2 = new OXHorizontal3dLine(vf1), Lr);

    //--- last accessed, last modified...

    vf1->AddFrame(d2 = new OX2ColumnsFrame(vf1, 10, 10), Lc);

    d2->AddFrame(lacc0 = new OXLabel(d2, new OString("Last accessed:")), Lc);
    d2->AddFrame(lacc = new OXLabel(d2, new OString("Unknown")), Lc);
    d2->AddFrame(lmod0 = new OXLabel(d2, new OString("Last modified:")), Lc);
    d2->AddFrame(lmod = new OXLabel(d2, new OString("Unknown")), Lc);

    //--- add the vertical frame to the tab

    tf->AddFrame(vf1, Vly);



    //--- Tab 2: file access permissions

    tf = tab->AddTab(new OString("Attributes"));

    vf2 = new OXVerticalFrame(tf, 10, 10);

    //--- file access permissions controls

    Lc1 = new OLayoutHints(LHINTS_LEFT | LHINTS_TOP, 0, 0, 5, 0);
    vf2->AddFrame(laccp = new OXLabel(vf2,
                          new OString("Access permissions:")), Lc1);
    vf2->AddFrame(hf2 = new OXHorizontalFrame(vf2, 10, 10), Lr);

    umode = new OXGroupFrame(hf2, new OString("User"));
    gmode = new OXGroupFrame(hf2, new OString("Group"));
    omode = new OXGroupFrame(hf2, new OString("Others"));
    Lg = new OLayoutHints(LHINTS_EXPAND_X | LHINTS_EXPAND_Y, 1, 1, 0, 0);
    hf2->AddFrame(umode, Lg);
    hf2->AddFrame(gmode, Lg);
    hf2->AddFrame(omode, Lg);

    ur = new OXCheckButton(umode, new OHotString("Read"),    71);
    uw = new OXCheckButton(umode, new OHotString("Write"),   72);
    ux = new OXCheckButton(umode, new OHotString("Execute"), 73);
    gr = new OXCheckButton(gmode, new OHotString("Read"),    74);
    gw = new OXCheckButton(gmode, new OHotString("Write"),   75);
    gx = new OXCheckButton(gmode, new OHotString("Execute"), 76);
    or = new OXCheckButton(omode, new OHotString("Read"),    77);
    ow = new OXCheckButton(omode, new OHotString("Write"),   78);
    ox = new OXCheckButton(omode, new OHotString("Execute"), 79);

    ur->Associate(this);
    uw->Associate(this);
    ux->Associate(this);
    gr->Associate(this);
    gw->Associate(this);
    gx->Associate(this);
    or->Associate(this);
    ow->Associate(this);
    ox->Associate(this);

    umode->AddFrame(ur, Lc1);
    umode->AddFrame(uw, Lc1);
    umode->AddFrame(ux, Lc1);
    gmode->AddFrame(gr, Lc1);
    gmode->AddFrame(gw, Lc1);
    gmode->AddFrame(gx, Lc1);
    omode->AddFrame(or, Lc1);
    omode->AddFrame(ow, Lc1);
    omode->AddFrame(ox, Lc1);

    Ls = new OLayoutHints(LHINTS_EXPAND_X | LHINTS_TOP, 0, 0, 5, 0);
    vf2->AddFrame(sticky = new OXCheckButton(vf2, new OHotString("Sticky"), 80), Ls);
    vf2->AddFrame(suid = new OXCheckButton(vf2, new OHotString("Set user ID on exec"), 81), Ls);
    vf2->AddFrame(sgid = new OXCheckButton(vf2, new OHotString("Set group ID on exec"), 82), Ls);

    sticky->Associate(this);
    suid->Associate(this);
    sgid->Associate(this);

    //--- add the vertical frame to the tab
    tf->AddFrame(vf2, Vly);

    //--- add the tab to the dialog
    AddFrame(tab, Ltab);

    InitControls();

    MapSubwindows();

    width  = 330; // GetDefaultWidth();
    height = 336; // GetDefaultHeight();
    Resize(width, height);

    //---- position relative to the parent's window (the default was root 0,0!)
 
    XTranslateCoordinates(GetDisplay(),
                          main->GetId(), GetParent()->GetId(),
                          50, 50, &ax, &ay, &wdummy);

    Move(ax, ay);

    //---- make dialog non-resizable

    SetWMSize(width, height);
    SetWMSizeHints(width, height, width, height, 0, 0);

    sprintf(wname, "%s Properties", name);
    SetWindowName(wname);
    SetIconName("Properties");
    SetClassHints("Explorer", "Explorer");

    SetMWMHints(MWM_DECOR_ALL | MWM_DECOR_RESIZEH | MWM_DECOR_MAXIMIZE /*|
                                MWM_DECOR_MINIMIZE*/ | MWM_DECOR_MENU,
                MWM_FUNC_ALL | MWM_FUNC_RESIZE | MWM_FUNC_MAXIMIZE /*| 
                               MWM_FUNC_MINIMIZE*/,
                MWM_INPUT_MODELESS);

    MapWindow();
//    _client->WaitFor(this);
}

OXPropertiesDialog::~OXPropertiesDialog() {

  delete Ok; delete Cancel; delete Apply;
  delete bframe; delete bly; delete bfly;

  delete ltype; delete ltype0;
  delete ldev;  delete ldev0;
  delete lusr;  delete lusr0;
  delete lgrp;  delete lgrp0;
  delete llnks; delete llnks0;
  delete lsz;   delete lsz0;
  delete lacc;  delete lacc0;
  delete lmod;  delete lmod0;

  delete FileIcon; delete Ily;
  delete FileName; delete Tly;

  delete vf1; delete hf1; delete lhf1;
  delete ln1; delete ln2;
  delete d1;  delete d2;

  delete laccp;

  delete ur; delete uw; delete ux;
  delete gr; delete gw; delete gx;
  delete or; delete ow; delete ox;

  delete sticky; delete suid; delete sgid;

  delete umode; delete gmode; delete omode;

  delete hf2; delete Lr;
  delete vf2; delete Vly;

  delete Lc; delete Lc1;
  delete Lg; delete Ls;

  delete tab; delete Ltab;
  delete _filename;
}

void OXPropertiesDialog::CloseWindow() {
  delete this;
}

void OXPropertiesDialog::InitControls() {
  struct stat sbuf;
  unsigned long bsize, fsize;
  char *ftype, tmp[256], *tformat;
  struct tm *tms;
  struct group *grp;
  struct passwd *pwd;

  bfr1->Clear();
  bfr1->AddText(0, _filename->GetString());
  _client->NeedRedraw(FileName);

  if (stat(_filename->GetString(), &sbuf) == -1) {
    sprintf(tmp, "Can't read file attributes of \"%s\": %s.",
                 _filename->GetString(), strerror(errno));
    new OXMsgBox(_client->GetRoot(), this,
                 new OString("Error"), new OString(tmp),
                 MB_ICONSTOP, ID_OK);
    return;
  }

  // type of file...
  if (S_ISLNK(sbuf.st_mode)) ftype = "Soft Link";
  else if (S_ISREG(sbuf.st_mode)) ftype = "Regular file";
  else if (S_ISDIR(sbuf.st_mode)) ftype = "File folder";
  else if (S_ISCHR(sbuf.st_mode)) ftype = "Character device";
  else if (S_ISBLK(sbuf.st_mode)) ftype = "Block device";
  else if (S_ISFIFO(sbuf.st_mode)) ftype = "FIFO";
  else if (S_ISSOCK(sbuf.st_mode)) ftype = "Socket";
  else ftype = "Unknown";

  sprintf(tmp, "%s%s", _is_link ? "Soft Link to " : "", ftype);
  ltype->SetText(new OString(tmp));

  sprintf(tmp, "device %d,%d", (int) (sbuf.st_dev/256),
                               (int) (sbuf.st_dev%256));
  ldev->SetText(new OString(tmp));

  // file ownership...
  pwd = getpwuid(sbuf.st_uid);
  if (pwd) {
    lusr->SetText(new OString(pwd->pw_name));
  } else {
    sprintf(tmp, "%d", sbuf.st_uid);
    lusr->SetText(new OString(tmp));
  }
  grp = getgrgid(sbuf.st_gid);
  if (grp) {
    lgrp->SetText(new OString(grp->gr_name));
  } else {
    sprintf(tmp, "%d", sbuf.st_gid);
    lusr->SetText(new OString(tmp));
  }

  // links
  sprintf(tmp, "%ld", sbuf.st_nlink);
  llnks->SetText(new OString(tmp));

  // file size
  fsize = bsize = sbuf.st_size;
  if (fsize > 1024) {
    fsize /= 1024;
    if (fsize > 1024) {
      // 3.7MB is more informative than just 3MB
      sprintf(tmp, "%ld.%ld MB (%ld bytes)", fsize/1024, (fsize%1024)/103, bsize);
    } else {
      sprintf(tmp, "%ld.%ld KB (%ld bytes)", bsize/1024, (bsize%1024)/103, bsize);
    }
  } else {
    sprintf(tmp, "%ld bytes", bsize);
  }
  lsz->SetText(new OString(tmp));

  // last access and modification...
  tformat = "%b %d, %Y  %R";
  tms = localtime(&sbuf.st_atime);
  strftime(tmp, 255, tformat, tms);
  lacc->SetText(new OString(tmp));
  tms = localtime(&sbuf.st_mtime);
  strftime(tmp, 255, tformat, tms);
  lmod->SetText(new OString(tmp));

  // access permissions...
  ur->SetState((sbuf.st_mode & S_IRUSR) ? BUTTON_DOWN : BUTTON_UP);
  uw->SetState((sbuf.st_mode & S_IWUSR) ? BUTTON_DOWN : BUTTON_UP);
  ux->SetState((sbuf.st_mode & S_IXUSR) ? BUTTON_DOWN : BUTTON_UP);
  gr->SetState((sbuf.st_mode & S_IRGRP) ? BUTTON_DOWN : BUTTON_UP);
  gw->SetState((sbuf.st_mode & S_IWGRP) ? BUTTON_DOWN : BUTTON_UP);
  gx->SetState((sbuf.st_mode & S_IXGRP) ? BUTTON_DOWN : BUTTON_UP);
  or->SetState((sbuf.st_mode & S_IROTH) ? BUTTON_DOWN : BUTTON_UP);
  ow->SetState((sbuf.st_mode & S_IWOTH) ? BUTTON_DOWN : BUTTON_UP);
  ox->SetState((sbuf.st_mode & S_IXOTH) ? BUTTON_DOWN : BUTTON_UP);

  sticky->SetState((sbuf.st_mode & S_ISVTX) ? BUTTON_DOWN : BUTTON_UP);
  suid->SetState((sbuf.st_mode & S_ISUID) ? BUTTON_DOWN : BUTTON_UP);
  sgid->SetState((sbuf.st_mode & S_ISGID) ? BUTTON_DOWN : BUTTON_UP);

  Layout();
}

void OXPropertiesDialog::ApplyProperties() {
  mode_t newstate = 0;
  char msg[256];

  if (strcmp(_filename->GetString(), bfr1->GetString()) != 0) {
    if (access(bfr1->GetString(), F_OK) == 0) {
      int retval;

      new OXMsgBox(_client->GetRoot(), this,
            new OString("Rename"),
            new OString("A file with the same name already exists. Overwrite?"),
            MB_ICONQUESTION, ID_YES | ID_NO, &retval);

      if (retval != ID_YES) {
        InitControls();  // restore old filename
        return;
      }
    }

    if (rename(_filename->GetString(), bfr1->GetString()) != 0) {
      sprintf(msg, "Can't rename file: %s.", strerror(errno));
      new OXMsgBox(_client->GetRoot(), this,
                   new OString("Error"), new OString(msg),
                   MB_ICONSTOP, ID_OK);
      InitControls();
      return;
    }
    delete _filename;
    _filename = new OString(bfr1->GetString());
    fileWindow->DisplayDirectory();
  }

  if (ur->GetState() == BUTTON_DOWN) newstate = newstate | S_IRUSR;
  if (uw->GetState() == BUTTON_DOWN) newstate = newstate | S_IWUSR;
  if (ux->GetState() == BUTTON_DOWN) newstate = newstate | S_IXUSR;
  if (gr->GetState() == BUTTON_DOWN) newstate = newstate | S_IRGRP;
  if (gw->GetState() == BUTTON_DOWN) newstate = newstate | S_IWGRP;
  if (gx->GetState() == BUTTON_DOWN) newstate = newstate | S_IXGRP;
  if (or->GetState() == BUTTON_DOWN) newstate = newstate | S_IROTH;
  if (ow->GetState() == BUTTON_DOWN) newstate = newstate | S_IWOTH;
  if (ox->GetState() == BUTTON_DOWN) newstate = newstate | S_IXOTH;
  if (sticky->GetState() == BUTTON_DOWN) newstate = newstate | S_ISVTX;
  if (suid->GetState() == BUTTON_DOWN) newstate = newstate | S_ISUID;
  if (sgid->GetState() == BUTTON_DOWN) newstate = newstate | S_ISGID;

  if (chmod(_filename->GetString(), newstate) != 0){
    sprintf(msg, "Can't change file mode: %s.", strerror(errno));
    new OXMsgBox(_client->GetRoot(), this,
                 new OString("Error"), new OString(msg),
                 MB_ICONSTOP, ID_OK);
    InitControls();
  }
}

OXPropertiesDialog::ProcessMessage(long msg, long parm1, long parm2) {
  switch(GET_MSG(msg)) {
    case XC_COMMAND:
      switch(GET_SUBMSG(msg)) {
        case CM_BUTTON:
          switch(parm1) {
            case ID_OK:
              ApplyProperties();
              CloseWindow();
              break;

            case ID_CANCEL:
              CloseWindow();
              break;

            case ID_APPLY:
              ApplyProperties();
              Apply->SetState(BUTTON_DISABLED);
              break;
          }
          break;

        case CM_RADIOBUTTON:
        case CM_CHECKBUTTON:
          switch(parm1) {

          }
          Apply->SetState(BUTTON_UP);
          break;

        default:
          break;
      }
      break;

    case XC_TEXTENTRY:
      switch (GET_SUBMSG(msg)) {
        case TE_TEXTCHANGED:
          if (parm1 == 101) Apply->SetState(BUTTON_UP);
          break;

        default:
          break;
      }
      break;

    default:
      break;
  }

  return True;
}

void OX2ColumnsFrame::Layout() {
  SListFrameElt *ptr;
  int h, hf;

  _x1 = 0;
  _x2 = 0;

  // calculate column separator
  for (ptr=_flist; ptr != NULL; ptr = ptr->next) {
    _x2 = max(_x2, ptr->frame->GetDefaultWidth());
    // skip next frame
    ptr = ptr->next;
  }

  _x2 += 10;

  // now position frame pairs:
  h = 0;
  for (ptr=_flist; ptr != NULL; ptr = ptr->next) {
    ODimension csize;

    // first frame
    csize = ptr->frame->GetDefaultSize();
    ptr->frame->MoveResize(_x1, h, csize.w, csize.h);
    hf = csize.h;

    ptr = ptr->next;
    if (ptr == NULL) break;

    // second frame
    csize = ptr->frame->GetDefaultSize();
    ptr->frame->MoveResize(_x2, h, csize.w, csize.h);
    hf = max(hf, csize.h);
    h += hf;
  }
}

ODimension OX2ColumnsFrame::GetDefaultSize() const {
  SListFrameElt *ptr;
  ODimension csize;
  int w1, w2, h, hf;

  w1 = w2 = h = 0;

  // calculate column separator
  for (ptr=_flist; ptr != NULL; ptr = ptr->next) {
    csize = ptr->frame->GetDefaultSize();
    w1 = max(w1, csize.w);
    hf = csize.h;
    ptr = ptr->next;
    if (ptr == NULL) break;
    csize = ptr->frame->GetDefaultSize();
    w2 = max(w2, csize.w);
    hf = max(hf, csize.h);
    h += hf;
  }

  return ODimension(w1 + 10 + w2, h);
}
