/*
  maxEggExpOptions.cxx
  Created by Phillip Saltzman, 2/15/05
  Carnegie Mellon University, Entetainment Technology Center

  This file implements the classes that are used to choose
  what to export from 3D Studio max
*/

#include "maxEgg.h"

//Disable the forcing int to true or false performance warning
#pragma warning(disable: 4800)

void SetICustEdit(HWND wnd, int nIDDlgItem, char *text)
{ 
  ICustEdit *edit = GetICustEdit(GetDlgItem(wnd, nIDDlgItem));
  edit->SetText(text);
  ReleaseICustEdit(edit);
}

void SetICustEdit(HWND wnd, int nIDDlgItem, int n)
{
  char text[80];
  sprintf(text, "%d", n);
  ICustEdit *edit = GetICustEdit(GetDlgItem(wnd, nIDDlgItem));
  edit->SetText(text);
  ReleaseICustEdit(edit);
}

char *GetICustEditT(HWND wnd)
{
  static char buffer[2084];
  ICustEdit *edit = GetICustEdit(wnd);
  edit->GetText(buffer,2084);
  ReleaseICustEdit(edit);
  return buffer;
}

int GetICustEditI(HWND wnd, BOOL *valid)
{
  ICustEdit *edit = GetICustEdit(wnd);
  int i = edit->GetInt(valid);
  ReleaseICustEdit(edit);
  return i;
}

void ChunkSave(ISave *isave, int chunkid, int value)
{
  ULONG nb;
  isave->BeginChunk(chunkid);
  isave->Write(&value, sizeof(int), &nb);
  isave->EndChunk();
}

void ChunkSave(ISave *isave, int chunkid, ULONG value)
{
  ULONG nb;
  isave->BeginChunk(chunkid);
  isave->Write(&value, sizeof(ULONG), &nb);
  isave->EndChunk();
}

void ChunkSave(ISave *isave, int chunkid, bool value)
{
  ULONG nb;
  isave->BeginChunk(chunkid);
  isave->Write(&value, sizeof(bool), &nb);
  isave->EndChunk();
}

void ChunkSave(ISave *isave, int chunkid, char *value)
{
  ULONG nb;
  isave->BeginChunk(chunkid);
  int bytes = strlen(value) + 1;
  isave->Write(&bytes, sizeof(int), &nb);
  isave->Write(value, bytes, &nb);
  isave->EndChunk();
}

char *ChunkLoadString(ILoad *iload, char *buffer, int maxBytes)
{
  ULONG nb;
  int bytes;
  IOResult res;
  res = iload->Read(&bytes, sizeof(int), &nb);
  assert(res == IO_OK && bytes <= maxBytes);
  res = iload->Read(buffer, bytes, &nb);
  assert(res == IO_OK);
  return buffer;
}

int ChunkLoadInt(ILoad *iload)
{
  ULONG nb;
  int value;
  IOResult res;
  res = iload->Read(&value, sizeof(int), &nb);
  assert(res == IO_OK);
  return value;
}

int ChunkLoadULONG(ILoad *iload)
{
  ULONG nb, value;
  IOResult res;
  res = iload->Read(&value, sizeof(ULONG), &nb);
  assert(res == IO_OK);
  return value;
}

bool ChunkLoadBool(ILoad *iload)
{
  ULONG nb;
  bool value;
  IOResult res;
  res = iload->Read(&value, sizeof(bool), &nb);
  assert(res == IO_OK);
  return value;
}

void showAnimControls(HWND hWnd, BOOL val) {
  ShowWindow(GetDlgItem(hWnd, IDC_EF_LABEL), val);
  ShowWindow(GetDlgItem(hWnd, IDC_EF), val);
  ShowWindow(GetDlgItem(hWnd, IDC_SF_LABEL), val);
  SetWindowText(GetDlgItem(hWnd, IDC_EXP_SEL_FRAMES),
                val ? "Use Range:" : "Use Frame:");
}

void enableAnimControls(HWND hWnd, BOOL val) {
  EnableWindow(GetDlgItem(hWnd, IDC_SF_LABEL), val);
  EnableWindow(GetDlgItem(hWnd, IDC_SF), val);
  EnableWindow(GetDlgItem(hWnd, IDC_EF_LABEL), val);
  EnableWindow(GetDlgItem(hWnd, IDC_EF), val);
}

void enableChooserControls(HWND hWnd, BOOL val) {
  EnableWindow(GetDlgItem(hWnd, IDC_LIST_EXPORT), val);
  EnableWindow(GetDlgItem(hWnd, IDC_ADD_EXPORT), val);
  EnableWindow(GetDlgItem(hWnd, IDC_REMOVE_EXPORT), val);
}

#define ANIM_RAD_NONE 0
#define ANIM_RAD_EXPALL 1
#define ANIM_RAD_EXPSEL 2
#define ANIM_RAD_ALL    3
void enableAnimRadios(HWND hWnd, int mask) {
  EnableWindow(GetDlgItem(hWnd, IDC_EXP_ALL_FRAMES), mask & ANIM_RAD_EXPALL);
  EnableWindow(GetDlgItem(hWnd, IDC_EXP_SEL_FRAMES), mask & ANIM_RAD_EXPSEL);
}

// Handles the work of actually picking the target.
class AddNodeCB : public HitByNameDlgCallback
{
public:
  MaxOptionsDialog *ph; //Pointer to the parent class
  HWND hWnd;            //Handle to the parent dialog

  AddNodeCB (MaxOptionsDialog *instance, HWND wnd) : 
    ph(instance), hWnd(wnd) {}

  virtual TCHAR *dialogTitle() {return _T("Objects to Export");}
  virtual TCHAR *buttonText()  {return _T("Select");}
  virtual int filter(INode *node);
  virtual void proc(INodeTab &nodeTab);
};

//This tells what should be in the list
//Allow only triangular objects, nurbs, and joints
int AddNodeCB::filter(INode *node) {
  if (!node) return 0;
  
  Object *obj = node->EvalWorldState(0).obj;
  Control *c = node->GetTMController();
  NURBSSet getSet;
  bool is_bone = (node->GetBoneNodeOnOff() ||     //True for bones
     (c &&                                          //True for bipeds
     ((c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) ||
     (c->ClassID() == BIPBODY_CONTROL_CLASS_ID) ||
     (c->ClassID() == FOOTPRINT_CLASS_ID))));

  
  if (IsDlgButtonChecked(hWnd, IDC_ANIMATION) == BST_CHECKED)
    return is_bone && !ph->FindNode(node->GetHandle());
  else
    return (
    !is_bone &&
    ((obj->SuperClassID() == GEOMOBJECT_CLASS_ID && //Allow geometrics
      obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) ||
     (obj->SuperClassID() == SHAPE_CLASS_ID &&      //Allow CV NURBS
      obj->ClassID() == EDITABLE_SURF_CLASS_ID &&
      GetNURBSSet(obj, 0, getSet, TRUE) &&
      getSet.GetNURBSObject(0)->GetType() == kNCVCurve)) &&
    !ph->FindNode(node->GetHandle()));             //Only allow items not already selected
}

//Adds all of the selected items to the list
void AddNodeCB::proc(INodeTab &nodeTab) {
  for (int i = 0; i < nodeTab.Count(); i++)
    ph->AddNode(nodeTab[i]->GetHandle());
  ph->RefreshNodeList(hWnd);
}

//This callback class generates a list of nodes that have previously been selected
class RemoveNodeCB : public HitByNameDlgCallback
{
public:
    MaxOptionsDialog *ph; //Pointer to the parent class
    HWND hWnd;            //Handle to the parent dialog
    
    RemoveNodeCB (MaxOptionsDialog *instance, HWND wnd) : 
        ph(instance), hWnd(wnd) {}
    
    virtual TCHAR *dialogTitle() {return _T("Objects to Remove");}
    virtual TCHAR *buttonText()  {return _T("Remove");}
    virtual int filter(INode *node) {return (node && ph->FindNode(node->GetHandle()));}
    virtual void proc(INodeTab &nodeTab);
};


//Adds all of the selected items to the list
void RemoveNodeCB::proc(INodeTab &nodeTab) {
    for (int i = 0; i < nodeTab.Count(); i++)
        ph->RemoveNodeByHandle(nodeTab[i]->GetHandle());
    ph->RefreshNodeList(hWnd);
}

MaxEggOptions::MaxEggOptions() {
    _max_interface = NULL;
    _anim_type = MaxEggOptions::AT_model;
    _start_frame = INT_MIN;
    _end_frame = INT_MIN;
    _double_sided = false;
    _file_name[0]=0;
    _short_name[0]=0;
    _path_replace = new PathReplace;
    _path_replace->_path_store = PS_relative;
    _export_whole_scene = true;
    _export_all_frames = true;
    _successful = false;
}

BOOL CALLBACK MaxOptionsDialogProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) 
{
    char tempFilename[2048];
    //We pass in our plugin through the lParam variable. Let's convert it back.
    MaxOptionsDialog *imp = (MaxOptionsDialog*)GetWindowLongPtr(hWnd,GWLP_USERDATA); 
    if ( !imp && message != WM_INITDIALOG ) return FALSE;

    switch(message) {

    case WM_INITDIALOG:
        // this line is very necessary to pass the plugin as the lParam
        SetWindowLongPtr(hWnd,GWLP_USERDATA,lParam); 
        ((MaxOptionsDialog*)lParam)->UpdateUI(hWnd);
        return TRUE; break;
        
    case WM_CLOSE:
        EndDialog(hWnd, FALSE);
        return TRUE; break;
        
    case WM_COMMAND:
        //The modified control is found in the lower word of the wParam long.
        switch( LOWORD(wParam) ) {
        case IDC_MODEL:
            if (HIWORD(wParam) == BN_CLICKED) {
                SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED),
                              "Export Meshes:");
                enableAnimRadios(hWnd, ANIM_RAD_NONE);
                showAnimControls(hWnd, TRUE);
                enableAnimControls(hWnd, FALSE);
                if (imp->_prev_type == MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd);
                imp->_prev_type = MaxEggOptions::AT_model;
                return TRUE;
            }
            break;
            
        case IDC_ANIMATION:
            if (HIWORD(wParam) == BN_CLICKED) {
                SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED),
                              "Export Bones:");
                enableAnimRadios(hWnd, ANIM_RAD_ALL);
                showAnimControls(hWnd, TRUE);
                enableAnimControls(hWnd, IsDlgButtonChecked(hWnd, IDC_EXP_SEL_FRAMES));
                if (imp->_prev_type != MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd);
                imp->_prev_type = MaxEggOptions::AT_chan;
                return TRUE;
            }
            break;
        case IDC_BOTH:
            if (HIWORD(wParam) == BN_CLICKED) {
                SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED),
                              "Export Models:");
                enableAnimRadios(hWnd, ANIM_RAD_ALL);
                showAnimControls(hWnd, TRUE);
                enableAnimControls(hWnd, IsDlgButtonChecked(hWnd, IDC_EXP_SEL_FRAMES));
                if (imp->_prev_type == MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd);
                imp->_prev_type = MaxEggOptions::AT_both;
                return TRUE;
            }
            break;
        case IDC_POSE:
            if (HIWORD(wParam) == BN_CLICKED) {
                SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED),
                              "Export Meshes:");
                enableAnimRadios(hWnd, ANIM_RAD_EXPSEL);
                showAnimControls(hWnd, FALSE);
                enableAnimControls(hWnd, TRUE);
                CheckRadioButton(hWnd, IDC_EXP_ALL_FRAMES, IDC_EXP_SEL_FRAMES, IDC_EXP_SEL_FRAMES);
                if (imp->_prev_type == MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd);
                imp->_prev_type = MaxEggOptions::AT_pose;
                return TRUE;
            }
            break;
        case IDC_EXP_ALL_FRAMES:
            if (HIWORD(wParam) == BN_CLICKED) {
                enableAnimControls(hWnd, FALSE);
                return TRUE;
            }
            break;
            
        case IDC_EXP_SEL_FRAMES:
            if (HIWORD(wParam) == BN_CLICKED) {
                enableAnimControls(hWnd, TRUE);
                return TRUE;
            }
            break;

        case IDC_EXPORT_ALL:
            if (HIWORD(wParam) == BN_CLICKED) {
                enableChooserControls(hWnd, FALSE);
                return TRUE;
            }
            break;
            
        case IDC_EXPORT_SELECTED:
            if (HIWORD(wParam) == BN_CLICKED) {
                enableChooserControls(hWnd, TRUE);
                return TRUE;
            }
            break;

        case IDC_ADD_EXPORT:
            {
                if (!imp->_choosing_nodes) {
                    AddNodeCB PickCB(imp, hWnd);
                    imp->_choosing_nodes = true;
                    imp->_max_interface->DoHitByNameDialog(&PickCB);
                    imp->_choosing_nodes = false;
                }
            }
            return TRUE; break;

        case IDC_REMOVE_EXPORT:
            {
                if (!imp->_choosing_nodes) {
                    imp->_choosing_nodes = true;
                    RemoveNodeCB PickCB(imp, hWnd);
                    imp->_max_interface->DoHitByNameDialog(&PickCB);
                    imp->_choosing_nodes = false;
                }
            }
            return TRUE; break;

        case IDC_OK:
            if (imp->UpdateFromUI(hWnd)) EndDialog(hWnd, TRUE);
            return TRUE; break;
        case IDC_CANCEL:
            EndDialog(hWnd, FALSE);
            return TRUE; break;
        case IDC_BROWSE:
            OPENFILENAME ofn;
            strcpy(tempFilename, GetICustEditT(GetDlgItem(hWnd, IDC_FILENAME)));
            
            memset(&ofn, 0, sizeof(ofn));
            ofn.nMaxFile = 2047;
            ofn.lpstrFile = tempFilename;
            ofn.lStructSize = sizeof(ofn);
            ofn.hwndOwner = hWnd;
            ofn.Flags = OFN_HIDEREADONLY | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST;
            ofn.lpstrDefExt = "egg";
            ofn.lpstrFilter = "Panda3D Egg Files (*.egg)\0*.egg\0All Files (*.*)\0*.*\0";
            
            SetFocus(GetDlgItem(hWnd, IDC_FILENAME));
            if (GetSaveFileName(&ofn))
                SetICustEdit(hWnd, IDC_FILENAME, ofn.lpstrFile);
            //else {
            //  char buf[255];
            //  sprintf(buf, "%d", CommDlgExtendedError());
            //  MessageBox(hWnd, buf, "Error on GetSaveFileName", MB_OK);
            //}
            return TRUE; break;
        case IDC_CHECK1:
            if (IsDlgButtonChecked(hWnd, IDC_CHECK1))
                if (MessageBox(hWnd, "Warning: Exporting double-sided polygons can severely hurt "
                               "performance in Panda3D.\n\nAre you sure you want to export them?",
                               "Panda3D Exporter", MB_YESNO | MB_ICONQUESTION) != IDYES)
                    CheckDlgButton(hWnd, IDC_CHECK1, BST_UNCHECKED);
            return TRUE; break;
        default:
            //char buf[255];
            //sprintf(buf, "%d", LOWORD(wParam));
            //MessageBox(hWnd, buf, "Unknown WParam", MB_OK);
            break;
        }
    }
    return FALSE;
}

void MaxOptionsDialog::SetAnimRange() {
    // Get the start and end frames and the animation frame rate from Max
    Interval anim_range = _max_interface->GetAnimRange();
    _min_frame = anim_range.Start()/GetTicksPerFrame();
    _max_frame = anim_range.End()/GetTicksPerFrame();
}

MaxOptionsDialog::MaxOptionsDialog() :
    MaxEggOptions(),
    _min_frame(0),
    _max_frame(0),
    _checked(true),
    _choosing_nodes(false),
    _prev_type(AT_model)
{
}

MaxOptionsDialog::~MaxOptionsDialog ()
{
}


void MaxOptionsDialog::UpdateUI(HWND hWnd) {
    int typeButton = IDC_MODEL;
    int anim_exp = _export_all_frames ? IDC_EXP_ALL_FRAMES : IDC_EXP_SEL_FRAMES;
    int model_exp = _export_whole_scene ? IDC_EXPORT_ALL : IDC_EXPORT_SELECTED;
    
    switch (_anim_type) {
    case MaxEggOptions::AT_chan:  typeButton = IDC_ANIMATION; break;
    case MaxEggOptions::AT_both:  typeButton = IDC_BOTH; break;
    case MaxEggOptions::AT_pose:  typeButton = IDC_POSE; break;
    case MaxEggOptions::AT_model: typeButton = IDC_MODEL; break;
    }
    
    _prev_type = _anim_type;

    CheckRadioButton(hWnd, IDC_MODEL, IDC_POSE, typeButton);
    SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(typeButton, BN_CLICKED), 0);
    CheckRadioButton(hWnd, IDC_EXPORT_ALL, IDC_EXPORT_SELECTED, model_exp);
    SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(model_exp, BN_CLICKED), 0);
    CheckRadioButton(hWnd, IDC_EXP_ALL_FRAMES, IDC_EXP_SEL_FRAMES, anim_exp);
    SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(anim_exp, BN_CLICKED), 0);
    
    CheckDlgButton(hWnd, IDC_CHECK1,
                   _double_sided ? BST_CHECKED : BST_UNCHECKED);
    
    SetICustEdit(hWnd, IDC_FILENAME, _file_name);
    if (_start_frame != INT_MIN) {
        SetICustEdit(hWnd, IDC_SF, _start_frame);
        SetICustEdit(hWnd, IDC_EF, _end_frame);
    } else {
        SetICustEdit(hWnd, IDC_SF, _min_frame);
        SetICustEdit(hWnd, IDC_EF, _max_frame);
    }
    
    RefreshNodeList(hWnd);
}

void MaxOptionsDialog::ClearNodeList(HWND hWnd) {
    _node_list.clear();
    RefreshNodeList(hWnd);
}

void MaxOptionsDialog::RefreshNodeList(HWND hWnd) {
  //Clear and repopulate the node box
  HWND nodeLB = GetDlgItem(hWnd, IDC_LIST_EXPORT);
  SendMessage(nodeLB, LB_RESETCONTENT, 0, 0);
  for (int i = 0; i < _node_list.size(); i++) {
      INode *temp = _max_interface->GetINodeByHandle(_node_list[i]);
      TCHAR *name = _T("Unknown Node");
      if (temp) name = temp->GetName();
      SendMessage(nodeLB, LB_ADDSTRING, 0, (LPARAM)name);
  }
}

bool MaxOptionsDialog::UpdateFromUI(HWND hWnd) {
  BOOL valid;
  Anim_Type newAnimType;
  int newSF = INT_MIN, newEF = INT_MIN;
  char msg[1024];

  if (IsDlgButtonChecked(hWnd, IDC_MODEL))          newAnimType = MaxEggOptions::AT_model;
  else if (IsDlgButtonChecked(hWnd, IDC_ANIMATION)) newAnimType = MaxEggOptions::AT_chan;
  else if (IsDlgButtonChecked(hWnd, IDC_BOTH))      newAnimType = MaxEggOptions::AT_both;
  else                                              newAnimType = MaxEggOptions::AT_pose;

  if (newAnimType != MaxEggOptions::AT_model && IsDlgButtonChecked(hWnd, IDC_EXP_SEL_FRAMES)) {
      newSF = GetICustEditI(GetDlgItem(hWnd, IDC_SF), &valid);
      if (!valid) {
          MessageBox(hWnd, "Start Frame must be an integer", "Invalid Value", MB_OK | MB_ICONEXCLAMATION);
          return false;
      }
      if (newSF < _min_frame) {
          sprintf(msg, "Start Frame must be at least %d", _min_frame);
          MessageBox(hWnd, msg, "Invalid Value", MB_OK | MB_ICONEXCLAMATION);
          return false;
      }
      if (newSF > _max_frame) {
          sprintf(msg, "Start Frame must be at most %d", _max_frame);
          MessageBox(hWnd, msg, "Invalid Value", MB_OK | MB_ICONEXCLAMATION);
          return false;
      }
      if (newAnimType != MaxEggOptions::AT_pose) {
          newEF = GetICustEditI(GetDlgItem(hWnd, IDC_EF), &valid);
          if (!valid) {
              MessageBox(hWnd, "End Frame must be an integer", "Invalid Value", MB_OK | MB_ICONEXCLAMATION);
              return false;
          }
          if (newEF > _max_frame) {
              sprintf(msg, "End Frame must be at most %d", _max_frame);
              MessageBox(hWnd, msg, "Invalid Value", MB_OK | MB_ICONEXCLAMATION);
              return false;
          }
          if (newEF < newSF) {
              MessageBox(hWnd, "End Frame must be greater than the start frame", "Invalid Value", MB_OK | MB_ICONEXCLAMATION);
              return false;
          }
      }
  }

  char *temp = GetICustEditT(GetDlgItem(hWnd, IDC_FILENAME));
  if (!strlen(temp)) {
    MessageBox(hWnd, "The filename cannot be empty", "Invalid Value", MB_OK | MB_ICONEXCLAMATION);
    return false;
  }

  if (strlen(temp) < 4 || strncmp(".egg", temp+(strlen(temp) - 4), 4))
      sprintf(_file_name, "%s.egg", temp);
  else strcpy(_file_name, temp);

  temp = strrchr(_file_name, '\\');
  if (!temp) temp = _file_name;
  else temp++;

  if (strlen(temp) > sizeof(_short_name))
    sprintf(_short_name, "%.*s...", sizeof(_short_name)-4, temp);
  else {
    strcpy(_short_name, temp);
    _short_name[strlen(_short_name) - 4] = NULL; //Cut off the .egg
  }
  
  _start_frame = newSF;
  _end_frame = newEF;
  _anim_type = newAnimType;
  _double_sided = IsDlgButtonChecked(hWnd, IDC_CHECK1);
  _export_whole_scene = IsDlgButtonChecked(hWnd, IDC_EXPORT_ALL);
  _export_all_frames = IsDlgButtonChecked(hWnd, IDC_EXP_ALL_FRAMES);

  return true;
}

bool MaxOptionsDialog::FindNode(ULONG INodeHandle) {
    for (int i = 0; i < _node_list.size(); i++) 
        if (_node_list[i] == INodeHandle) return true;
    return false;
}

void MaxOptionsDialog::AddNode(ULONG INodeHandle) {
  if (FindNode(INodeHandle)) return; 
  _node_list.push_back(INodeHandle);
}

void MaxOptionsDialog::CullBadNodes() {
  if (!_max_interface) return;
  std::vector<ULONG> good;
  for (int i=0; i<_node_list.size(); i++) {
      ULONG handle = _node_list[i];
      if (_max_interface->GetINodeByHandle(handle)) {
          good.push_back(handle);
      }
  }
  _node_list = good;
}

void MaxOptionsDialog::RemoveNode(int i) {
    if (i >= 0 && i < _node_list.size()) {
        for (int j = i+1; j < _node_list.size(); j++)
            _node_list[i++] = _node_list[j++];
        _node_list.pop_back();
    }
}

void MaxOptionsDialog::RemoveNodeByHandle(ULONG INodeHandle) {
    for (int i = 0; i < _node_list.size(); i++) {
        if (_node_list[i] == INodeHandle) {
            RemoveNode(i);
            return;
        }
    }
}

IOResult MaxOptionsDialog::Save(ISave *isave) {
    isave->BeginChunk(CHUNK_EGG_EXP_OPTIONS);
    ChunkSave(isave, CHUNK_ANIM_TYPE, _anim_type);
    ChunkSave(isave, CHUNK_FILENAME, _file_name);
    ChunkSave(isave, CHUNK_SHORTNAME, _short_name);
    ChunkSave(isave, CHUNK_SF, _start_frame);
    ChunkSave(isave, CHUNK_EF, _end_frame);
    ChunkSave(isave, CHUNK_DBL_SIDED, _double_sided);
    ChunkSave(isave, CHUNK_EGG_CHECKED, _checked);
    ChunkSave(isave, CHUNK_EXPORT_FULL, _export_whole_scene);
    ChunkSave(isave, CHUNK_ALL_FRAMES, _export_all_frames);
    isave->BeginChunk(CHUNK_NODE_LIST);
    for (int i = 0; i < _node_list.size(); i++)
        ChunkSave(isave, CHUNK_NODE_HANDLE, _node_list[i]);
    isave->EndChunk();
    isave->EndChunk();
    return IO_OK;
} 

IOResult MaxOptionsDialog::Load(ILoad *iload) {
    IOResult res = iload->OpenChunk();
    
    while (res == IO_OK) {
        switch(iload->CurChunkID()) {
        case CHUNK_ANIM_TYPE: _anim_type = (Anim_Type)ChunkLoadInt(iload); break;
        case CHUNK_FILENAME: ChunkLoadString(iload, _file_name, sizeof(_file_name)); break;
        case CHUNK_SHORTNAME: ChunkLoadString(iload, _short_name, sizeof(_short_name)); break;
        case CHUNK_SF: _start_frame = ChunkLoadInt(iload); break;
        case CHUNK_EF: _end_frame = ChunkLoadInt(iload); break;
        case CHUNK_DBL_SIDED: _double_sided = ChunkLoadBool(iload); break;
        case CHUNK_EGG_CHECKED: _checked = ChunkLoadBool(iload); break;
        case CHUNK_EXPORT_FULL: _export_whole_scene = ChunkLoadBool(iload); break;
        case CHUNK_ALL_FRAMES: _export_all_frames = ChunkLoadBool(iload); break;
        case CHUNK_NODE_LIST:
            res = iload->OpenChunk();
            while (res == IO_OK) {
                if (iload->CurChunkID() == CHUNK_NODE_HANDLE) AddNode(ChunkLoadULONG(iload));
                iload->CloseChunk();
                res = iload->OpenChunk();
            }
            break;
        }
        iload->CloseChunk();
        res = iload->OpenChunk();
    }
    
    if (res == IO_END) return IO_OK;
    return IO_ERROR;
}

