// $Id: sheet_io.cc,v 1.31 1998/09/29 01:22:25 cthulhu Exp $

#include <unistd.h>
#include <fcntl.h>
#include "sheet.hh"


#define LOAD_SHORT(var,address) { \
                                   memcpy(&(var),address,sizeof(short));\
                               }
#define LOAD_DOUBLE(var,address) { \
                                    memcpy(&(var),address,sizeof(double));\
                                }



int Sheet::load(char *filename) {
    int flin;
    int n_read;
    short record_code;
    short record_length;
    int done = FALSE;
    unsigned char buf[512];
    short row,col;
    char format;
    short fsize;
    Cell *cell;
    Sheet_format fmr;
    int cell_cnt=0;
    int format_cnt=0;
    int colrow_format=0;

    empty = FALSE;
    flin = open(filename,O_RDONLY,0);

    if (flin == -1)
        return(XXL_BAD_FILE);

    for(;!done;) {
        cell = NULL;

        n_read = read(flin,&record_code,2);
        if (n_read != 2)
            return(XXL_BAD_RECORD);

        n_read = read(flin,&record_length,2);
        if (n_read != 2)
            return(XXL_BAD_RECORD);

        n_read = read(flin,buf,record_length);
        if (n_read != record_length)
            return(XXL_BAD_RECORD);

        switch(record_code) {
          case CODE_BOF:
            break;
          case CODE_RECALC:
            automatic_recalc = *buf;
            break;
          case CODE_EOF:
            done = TRUE;
            break;
          case CODE_BLANK:
            break;

          case CODE_INTEGER:
            LOAD_SHORT(col,buf+1);
            LOAD_SHORT(row,buf+3);
            cell = cellFind(col,row);
            if (*buf != DEFAULT_FORMAT) {
                cell->format.form = *buf;
            }
            LOAD_SHORT(cell->ivalue,buf+5);
            cell->type = CODE_INTEGER;
            cell_cnt++;
            break;

          case CODE_NUMBER :
            LOAD_SHORT(col,buf+1);
            LOAD_SHORT(row,buf+3);
            cell = cellFind(col,row);
            if (*buf != DEFAULT_FORMAT) {
                cell->format.form = *buf;
            }
            LOAD_DOUBLE(cell->value,buf+5);
            cell->type = CODE_NUMBER;
            cell_cnt++;
            break;

          case CODE_LABEL:
            LOAD_SHORT(col,buf+1);
            LOAD_SHORT(row,buf+3);
            cell = cellFind(col,row);
            if (*buf != DEFAULT_FORMAT) {
                cell->format.form = *buf;
            }
            cell->label = strsave((char*)(buf+5));
            cell->type = CODE_LABEL;
            cell_cnt++;
            break;

          case CODE_CELL_FONT:
            LOAD_SHORT(col,buf+1);
            LOAD_SHORT(row,buf+3);
            cell = cellFind(col,row);
            if (cell == NULL) {
              cerr << "Cell font without cell being specified\n";
              exit(0);
            }
            memcpy((char*)&(cell->format.font),buf+5,4);
            format_cnt++;
            break;

          case CODE_CELL_FORMAT:
            LOAD_SHORT(col,buf+1);
            LOAD_SHORT(row,buf+3);
            cell = cellFind(col,row);
            if (cell == NULL) {
              cerr << "Cell format without cell being specified\n";
              exit(0);
            }
            cell->format.alignment = *buf;
            memcpy((char*)&(cell->format.bg),buf+5,4);
            memcpy((char*)&(cell->format.borders),buf+9,4);
            format_cnt++;
            break;
            

          case CODE_STRING_VAL:
            LOAD_SHORT(col,buf+1);
            LOAD_SHORT(row,buf+3);
            cell = cellFind(col,row);
            if (cell == NULL || cell->type != CODE_FORMULA) {
              cerr << "String value found without formula was specified\n";
              exit(0);
            }
            cell->formula->st_value = strsave((char*)(buf+5));
            cell->formula->typ = FORM_STRING;
            cell_cnt++;
            break;

          case CODE_FORMULA:
            LOAD_SHORT(col,buf+1);
            LOAD_SHORT(row,buf+3);
            cell = cellFind(col,row);
            LOAD_DOUBLE(cell->value,buf+5);
            LOAD_SHORT(fsize,buf+13);
            if (*buf != DEFAULT_FORMAT) {
                cell->format.form = *buf;
            }
            cell->formula = new Formula(fsize,(char*)(buf+15));
            cell->type = CODE_FORMULA;
            manipulateReferences(cell,0);
            cell_cnt++;
            break;

          case CODE_COLW1:
            fmr.sh_val1 = *(short*)(buf);
            fmr.c_val1 = *(unsigned char *)(buf+2);
            set_col_width(fmr.sh_val1,fmr.c_val1*DEFAULT_FONT_WIDTH);
            colrow_format++;
            break;

          case CODE_COLW2:
            fmr.sh_val1 = *(short*)(buf);
            fmr.c_val1 = *(unsigned char *)(buf+2);
            set_col_width(fmr.sh_val1,fmr.c_val1);
            colrow_format++;
            break;

          case CODE_COLW3:
            fmr.sh_val1 = *(short*)(buf);
            fmr.sh_val2 = *(short*)(buf+2);
            set_col_width(fmr.sh_val1,fmr.sh_val2);
            colrow_format++;
            break;

          case CODE_ROWH1:
            fmr.sh_val1 = *(short*)(buf);
            fmr.c_val1 = *(unsigned char *)(buf+2);
            set_row_height(fmr.sh_val1,fmr.c_val1);
            colrow_format++;
            break;

          case CODE_PRANGE:
            memcpy(&print_range,buf,8);
            break;

          case CODE_PSETTINGS:
            memcpy(&psettings,buf,sizeof(Print_Settings));
            break;

          case CODE_GRAPH:
            {
              Graph *grp = new Graph;
              grp->number = graphs_cnt;
              grp->iter = -1;
              graphs[graphs_cnt] = grp;
              memcpy(grp,buf,sizeof(Graph));
              add_graph_deps(grp);
              graphs_cnt++;
            }
            break;

          case CODE_NOCELLBORDERS:
            display_cell_borders = FALSE;
            break;

          default:
            break;
        }
        

    } 
    


    close(flin);
    modified_after_save = FALSE;
    return(XXL_OK);
}

/*
#define WRITE_SHORT(address,val) { \
                                   fp_short = (short*)(address);\
                                   *fp_short = val;\
                           }
#define WRITE_DOUBLE(address,val) { \
                                   fp_double = (double*)(address);\
                                   *fp_double = val;\
                           }
*/
#define WRITE_SHORT(address,val) { \
                                   memcpy(address,&(val),sizeof(short));\
                               }
#define WRITE_DOUBLE(address,val) { \
                                    memcpy(address,&(val),sizeof(double));\
                                }



int Sheet::save(char *filename,short query) {
    int flout;
    int n_read;
    short record_code;
    short record_length;
    int done = FALSE;
    unsigned char buf[256];
    short row,col;
    char format;
    short fsize;
    Cell *cell;
    Tcl_HashSearch pt;
    Tcl_HashEntry *entry;
    int i;
    int need_to_write_value_record = FALSE;
    int leng;
    short cst;


    int code;
    short *fp_short;
    double *fp_double;
    mode_t mode;

    /* First, recalculate spreadsheet */
    if (recalculate_before_saving && automatic_recalc == 0) {
      unsigned char prev_mode = automatic_recalc;
      automatic_recalc = 255;
      recalculate_list_of_cells();
      automatic_recalc = prev_mode;
    }



    flout = open(filename,O_RDONLY,0);
    if (query&&(flout != -1)) {
        sprintf((char*)buf,"queryUser {File %s exists.\nDo you want to overwrite it ? } {Yes Cancel}", filename);
        code = Tcl_Eval(interp,(char*)buf);

        if (code == TCL_OK) {
          if (interp->result[0] == '0')
            ;
          else if (interp->result[0] == '1') {
            sprintf((char*)buf,"warnUser {Sheet %s not saved ! }", (char*)name);
            code = Tcl_Eval(interp,(char*)buf);
            return(XXL_ABORT);
          }
        }
        else 
            internal_error();

    }


    
    mode = 0644;
    flout = creat(filename,mode);
    if (flout == -1)
        return(XXL_BAD_FILE);

    record_code = CODE_BOF; record_length = 2; buf[0] = 6; buf[1] = 4;
    code = write_record(flout,record_code,record_length,(char*)buf);
    if (code != XXL_OK) return(code);
    
    record_code = CODE_RECALC; record_length = 1; buf[0] = automatic_recalc;
    code = write_record(flout,record_code,record_length,(char*)buf);
    if (code != XXL_OK) return(code);


    /* Now, lets write the cells */

    for(entry = Tcl_FirstHashEntry(&cells,&pt); 
        entry ;
        entry = Tcl_NextHashEntry(&pt)) {

        cell = (Cell*)Tcl_GetHashValue(entry);
        
        record_code = cell->type;

        switch(cell->type) {
          case CODE_BLANK:
            break;
          case CODE_INTEGER:
            record_length = 7;
            *buf = cell->format.form;
            WRITE_SHORT(buf+1,cell->col); 
            WRITE_SHORT(buf+3,cell->row);
            WRITE_SHORT(buf+5,cell->ivalue);
            write_record(flout,record_code,record_length,(char*)buf);
            break;

          case CODE_NUMBER :
            record_length = 13;
            *buf = cell->format.form;
            WRITE_SHORT(buf+1,cell->col); 
            WRITE_SHORT(buf+3,cell->row);
            WRITE_DOUBLE(buf+5,cell->value);
            write_record(flout,record_code,record_length,(char*)buf);
            break;

          case CODE_LABEL:
            record_length = strlen(cell->label)+1+5;
            *buf = cell->format.form;
            WRITE_SHORT(buf+1,cell->col); 
            WRITE_SHORT(buf+3,cell->row);
            memcpy(buf+5,cell->label,record_length-5);
            write_record(flout,record_code,record_length,(char*)buf);
            break;

          case CODE_FORMULA:
            record_length = 15+cell->formula->size;
            *buf = cell->format.form;
            WRITE_SHORT(buf+1,cell->col); 
            WRITE_SHORT(buf+3,cell->row);
            WRITE_DOUBLE(buf+5,cell->value);
            WRITE_SHORT(buf+13,cell->formula->size);
            memcpy(buf+15,cell->formula->formula,cell->formula->size);
            write_record(flout,record_code,record_length,(char*)buf);
            if (cell->formula->typ == FORM_STRING){
                record_code = CODE_STRING_VAL;
                leng = strlen(cell->formula->st_value)+1;
                record_length = 5+leng;
                *buf = cell->format.form;
                WRITE_SHORT(buf+1,cell->col); 
                WRITE_SHORT(buf+3,cell->row);
                memcpy(buf+5,cell->formula->st_value,leng);
                write_record(flout,record_code,record_length,(char*)buf);
            }

            break;

          default:
            break;
        }

        if (!cell->format.font.isdefault()) {
            record_code = CODE_CELL_FONT;
            record_length = 9;
            *buf = cell->format.form;
            WRITE_SHORT(buf+1,cell->col); 
            WRITE_SHORT(buf+3,cell->row);
            memcpy(buf+5,(char*)&(cell->format.font),4);
            write_record(flout,record_code,record_length,(char*)buf);
        }
        if (!cell->format.bg.isdefault() || 
            !cell->format.borders.isdefault() ||
            cell->format.alignment != DEFAULT_ALIGNMENT) {
            record_code = CODE_CELL_FORMAT;
            record_length = 13;
            *buf = cell->format.alignment;
            WRITE_SHORT(buf+1,cell->col); 
            WRITE_SHORT(buf+3,cell->row);
            memcpy(buf+5,(char*)&(cell->format.bg),4);
            memcpy(buf+9,(char*)&(cell->format.borders),4);
            write_record(flout,record_code,record_length,(char*)buf);
        }
        
        

    }
    
    /* Now, let's write down special formats */


    for (cst=0; cst<DEFSHEETWIDTH; cst++) {
        short w;
        record_code = CODE_COLW3;
        record_length = 4;
        w = cell_width(cst);
        if (w != def_cell_width) {
            memcpy(buf,&cst,sizeof(short));
            memcpy(buf+2,&w,sizeof(short));
            write_record(flout,record_code,record_length,(char*)buf);
            
        }
    }

    for (cst=0; cst<DEFSHEETHEIGHT; cst++) {
        short w;
        record_code = CODE_ROWH1;
        record_length = 3;
        w = cell_height(cst);
        if (w != def_cell_height) {
            WRITE_SHORT(buf,cst);
            buf[2] = w;
            write_record(flout,record_code,record_length,(char*)buf);
        }
    }

    if (display_cell_borders == FALSE) {
      record_code = CODE_NOCELLBORDERS;
      record_length = 0;
      write_record(flout,record_code,record_length,NULL);
    }

    
    record_code = CODE_PRANGE;  record_length = 8;
    write_record(flout,record_code,record_length,
                 (char*)(&(print_range)));

    record_code = CODE_PSETTINGS;  record_length = sizeof(Print_Settings);
    write_record(flout,record_code,record_length,
                 (char*)(&(psettings)));

    
    record_code = CODE_GRAPH;  record_length = sizeof(Graph);
    for(i=0; i<MAX_GRAPHS; i++) {
      if (graphs[i]) {
        write_record(flout,record_code,record_length,
                     (char*)(graphs[i]));
      }
    }

    record_code = CODE_EOF; record_length = 0;
    write_record(flout,record_code,record_length,NULL);
    if (code != XXL_OK) return(code);

    close(flout);
    return(XXL_OK);
}

int Sheet::write_record(int fp, short code, short length, char *data){
    int n_b;

    n_b = write(fp,&code,2);
    if (n_b != 2)
        return(XXL_ERROR_WRIT);

    n_b = write(fp,&length,2);
    if (n_b != 2)
        return(XXL_ERROR_WRIT);

    if (length > 0){
        n_b = write(fp,data,length);
        if (n_b != length)
            return(XXL_ERROR_WRIT);
    }

    modified_after_save = FALSE;
    return(XXL_OK);

}

/* 
$Log: sheet_io.cc,v $
Revision 1.31  1998/09/29 01:22:25  cthulhu
Added query option to save so it can work silently (autosave).

Revision 1.30  1998/08/06 21:08:37  aml
Released alpha version of Abacus.

// Revision 1.29  1997/03/27  10:00:42  aml
// Started implementing graphs.
// Fixed bug in tkCanvasPs.c
// Created bindings for composite characters in Portuguese.
//
Revision 1.28  1997/02/10 15:10:53  aml
TODO: Improve print setup. Save it in the sheet. DONE V0.8.22

// Revision 1.27  1997/01/02  16:15:33  aml
// Fixed unsufficient range of colunm width.
// First cut of vlookup and hlookup functions.
// Fixed bug in display routines.
//
Revision 1.26  1996/12/31 17:38:39  aml
On-demand calculation improved. Loops are detected.
Automatic recalculation can now be disabled.
Printing was improved.

Revision 1.25  1996/12/11 21:40:01  aml
Sumif implemented.
Diverse time functions implemented.
Fixed needtoscroll2 to avoid out of control scroll.

// Revision 1.24  1996/11/22  16:29:15  aml
// First cut at transforming canvas into a true cell widget.
// Text, lines and rectangles are now relative to row and colunm numbers.
// It still has a bug with wrong estimation of column widths.
//
Revision 1.23  1996/09/15 19:24:29  aml
Rulling and shading.
Optionally hide cell borders.
Works well, but is very slow for large spreadsheets.

// Revision 1.22  1996/09/14  23:55:58  aml
// Created cell shading.
//
// Revision 1.21  1996/09/04  14:29:57  aml
// Fixed double redrawing of sheets that was taking place.
// Fixed a item reference problem when the sheet is redrawn.
// Fixed misplacement of row labels.
// Created first version of format toolbar.
//
Revision 1.20  1996/09/02 10:51:20  aml
Cell fonts created, loaded and saved.
Row height created.

// Revision 1.19  1996/08/28  17:17:51  aml
// Load and save now accept string_value for formula cells.
// This fixes previous thought problem of formula values not
// being stored.
// Functions upper,lower and proper created.
// Function if can now return labels.
// Fixed problem with function count.
// Reasonably stable version, very used to manipulate notas.wk1.
//
Revision 1.18  1996/08/26 12:08:46  aml
Fixed problem with several sheets. Each canvas now has its own
canvas info.
Fixed scroll up and down. Implemented max_row and max_col.
Fairly stable version.

// Revision 1.17  1996/07/18  10:19:26  aml
// Created formats for cells.
// Load cell now makes copy of old file.
//
// Revision 1.16  1996/04/23  09:42:53  aml
// Data structures for ordered scans of rows and columns are in place.
// Cell overlap is working.
// Forward cell dependences inserted. Automatic recalculation created.
// Uniformizaed label entry procedure.
//
Revision 1.15  1996/04/19 10:46:36  aml
First cut at speeding up canvas critical functions.
CanvasWidgetCommand is now called directly from draw_sheet.
Fixed bug in reading values from datafiles. Also works
for Suns now.
Created canvas directory, replacing builtin command canvas.

Revision 1.14  1996/03/29 21:45:40  aml
Changed key based scrolls to be synchronous. Work fine, but are somewhat slow.
Fixed abnormaly in state machine after range defition causing canvas scroll.
Solid, working version.

// Revision 1.13  1996/03/11  15:47:48  aml
// Made redraw more efficient by removing at once all cells before redrawing.
// Fixed problem with unsufficient difinition of logical expressions.
// Added >=, <=, <>, @and and @or functions.
//
Revision 1.12  1996/03/07 20:33:04  aml
Created print range ability.
Set in gray non-working menus.
Created RangeKill command.
Created round function and macros for single argument functions.

Revision 1.11  1996/03/06 20:25:25  aml
Improved user waiting times during redraw and loads by calling update.

Revision 1.10  1996/03/01 13:08:48  aml
Stack references are now pushed instead of double value.
Fixed problem with integers too large on formulas.
Scroll with cursors now works better.

Revision 1.9  1996/02/16 18:04:08  aml
Fixed a memory bug in formula copy with purify.
Fixed a few minor bugs. Functional, stable version.

Revision 1.8  1996/02/13 12:03:46  aml
Fixed bug with range definition via mouse.
Fixed bug in range iterators.

// Revision 1.7  1996/01/30  16:05:39  aml
// User interface for cell and range copy created.
// Improved user interface state machine when entering and viewing cells.
// Fixed unaligned accesses during formula parsing and io operations.
//
// Revision 1.6  1996/01/11  22:48:50  aml
// Range iterators created.
// Functions sum, max and min now work properly.
// Negative numbers now allowed by flex (oops :-)
//
// Revision 1.5  1996/01/10  18:54:33  aml
// Removed fixed format for cells when writing.
//
// Revision 1.4  1996/01/10  18:53:41  aml
// Fixed limited integer range of cells.
// Fixed incorrect code in spreadsheet type.
// Users interface improved. Cursors and mouse clicks
// now work in the most basic modes.
//
// Revision 1.3  1996/01/09  18:34:52  aml
// Load, save, open, close and exit now work properly (hopefuly).
// Sheet utility functions also work : SheetExists, SheetEmpty, SheetModified
//
// Revision 1.2  1996/01/07  09:07:38  aml
// Sheet::save and Sheet::load created.
// Program can now write and read wk1 files.
// Slight changes made to relative references. Bit 14 is now always 0.
//
// Revision 1.1  1996/01/06  20:44:35  aml
// Initial revision
//
*/
