/* apccomm_pc_tr.c
   Part of "APCComm" 
   Copyright (C) 2000,2001 Ralf Hoffmann
   Contact: ralf.hoffmann@epost.de

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  
*/
/* $Id: apccomm_pc_tr.c,v 1.9 2002/06/11 00:10:01 ralf Exp $ */

#include "apccomm_pc_tr.h"
#include "apccomm_pc.h"

int amiga_nibble;
int ack;

void setack(int value)
{
  change_pin(LP_PIN[3],(value==1)?LP_SET:LP_CLEAR);
}

int getack()
{
  ack=(pin_is_set(LP_PIN11)==0)?0:1;
  return ack;
}

int getnibble()
{
  int i,i2;
/*  i=(pin_is_set(LP_PIN10)==0)?0:8;
  i|=(pin_is_set(LP_PIN12)==0)?0:4;
  i|=(pin_is_set(LP_PIN13)==0)?0:2;
  i|=(pin_is_set(LP_PIN15)==0)?0:1;
  amiga_nibble=i;
  return i;*/
  i=pin_is_set(LP_PIN10|LP_PIN12|LP_PIN13|LP_PIN15);
  if(i&LP_PIN10) i2=8; else i2=0;
  if(i&LP_PIN12) i2|=4;
  if(i&LP_PIN13) i2|=2;
  if(i&LP_PIN15) i2|=1;
  amiga_nibble=i2;
  return i2;
}

void putvalue(int value)
{
  if(value==3) set_pin(LP_PIN06|LP_PIN04);
  else if(value==0) clear_pin(LP_PIN06|LP_PIN04);
  else {
    change_pin(LP_PIN06,(value&1)?LP_SET:LP_CLEAR);
    change_pin(LP_PIN04,(value&2)?LP_SET:LP_CLEAR);
  }
}

void synchro()
{
  int count;
  setack(1);
  putvalue(0);
/*  while(getack()!=1);*/
  for(count=0;getack()!=1;count++) {
    usleep(1000);
    if(count%100==0) {
      if ( ! BE_QUIET ) printf(".");
      fflush(stdout);
    }
  }
  setack(0);
/*  while(getnibble()!=1);*/
  for(count=0;getnibble()!=1;count++) {
    usleep(1000);
    if(count%100==0) {
      if ( ! BE_QUIET ) printf(".");
      fflush(stdout);
    }
  }
  putvalue(1);
/*  while(getnibble()!=2);*/
  for(count=0;getnibble()!=2;count++) {
    usleep(1000);
    if(count%100==0) {
      if ( ! BE_QUIET ) printf(".");
      fflush(stdout);
    }
  }
  putvalue(2);
  setack(1);
}

void transferblock()
{
  int i,s1;
  int outbyte=0,inbyte,bytecyc,ti;

  bytecyc=3;
  for(i=0;i<TRANSFERBLOCKSIZE;i++) {
    s1=0;
    if(bytecyc==3) outbyte=outblock[i>>1];
    s1=outbyte>>((bytecyc--)*2);
    while(getack()!=0);
    putvalue(s1&0x3);
    inbyte=getnibble();
    setack(0);
    s1=outbyte>>((bytecyc--)*2);
    while(getack()!=1);
    putvalue(s1&0x3);
    ti=getnibble();
    ti<<=4;
    inbyte|=ti;
    setack(1);
    if(bytecyc<0) bytecyc=3;
    inblock[i]=(unsigned char)inbyte;
  }
}

int recvcommand(apc_command_t *com,char **arg,int *mode)
{
  apc_command_t tc;
  char *narg;

  transferblock();
  tc=(apc_command_t)getint(inblock);
  switch(tc) {
    case APC_COMMAND_ERROR:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_ENTERDIR:
      if(arg!=NULL) {
	narg=(char*)malloc(TRANSFERBLOCKSIZE-4+1);
	if(narg!=NULL) {
	  *com=tc;
	  strncpy(narg,inblock+4,TRANSFERBLOCKSIZE-4-4);
	  narg[TRANSFERBLOCKSIZE-4-4]='\0';
	  *arg=narg;
	  *mode=getint(inblock+TRANSFERBLOCKSIZE-4);
	} else return 1;
      } else return 1;
      break;
    case APC_COMMAND_LEAVEDIR:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_FILETRANSFER:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      return recvfile();
      break;
    case APC_COMMAND_FINISHED:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_NOP:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_ABORT:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_SKIPFILE:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    default:
      fprintf( stderr, "Unknown command %d received!\n", tc );
      return 1;
      break;
  }
  return 0;
}

int recvfile()
{
  struct timeval tvs,tvi;
  int secs,usecs;
  int outchecksum,inchecksum,checksum,wrong,s1,tc;
  double d1;
  int getfile;
  int size=0,recbytes,count,l;
  char outbuf[128],recvbuf[128],*cwd,*tstr=NULL;
  int cwdsize;
  char *inarg=NULL;
  int ret;
  int mode,tm;

  size=getint(inblock+4);
  l=getint(inblock+8);

  inarg=(char*)malloc(l+1);
  if(inarg==NULL) return 1;
  strncpy(inarg,inblock+12,l);
  inarg[l]='\0';
  
  mode=getint(inblock+TRANSFERBLOCKSIZE-4);
  
  if ( ! BE_QUIET ) printf("receiving file \"%s\", size %d\n",inarg,size);
  ret = init_incoming_file(inarg);

  getfile=1;

  checksum=0;

  sprintf(outbuf,"%d",size);
  sprintf(recvbuf,"RECV: %%%dd/%%%dd (%%6.2f%%%%)",strlen(outbuf),strlen(outbuf));
    
  inchecksum=outchecksum=0;
  recbytes=0;
  wrong=0;
  tc = 0;

  if((ret>0)||(ret==-2)) {
    fprintf( stderr, "Can't create %s in %s, aborting\n", inarg, basedir );
    sendcommand(APC_COMMAND_ABORT,NULL, 0 );
  } else if(ret==-1) {
    sendcommand(APC_COMMAND_SKIPFILE,NULL, 0 );
  } else if(ret==0) {
    sendcommand(APC_COMMAND_NOP,NULL, 0 );

    gettimeofday(&tvs,NULL);
    for(count=0;getfile==1;count++) {
      if(recbytes<size) {
        if(wrong==0) putint(outblock+TRANSFERBLOCKSIZE/2-4,0);
	else putint(outblock+TRANSFERBLOCKSIZE/2-4,0xffffffff);

	if(getfile==1) {
	  transferblock();
	  tc=getint(inblock+TRANSFERBLOCKSIZE-4);
	  if(tc==0xffffffff) wrong=2;
          else if ( tc == 0xffffffee ) wrong = 4;
	}
	if(wrong!=0) break;

	s1=size-recbytes;
	if(s1>TRANSFERBLOCKSIZE-4) s1=TRANSFERBLOCKSIZE-4;
	else getfile=2;

	writefrombuffer(inblock,s1,&inchecksum);
	recbytes+=s1;
	checksum=inchecksum-tc;
	if(checksum!=0) wrong=1;
      } else getfile = 2;

      if ( ( count == 19 ) || ( getfile != 1 ) ) {
	count=0;
	if ( size )
	  sprintf(outbuf,recvbuf,recbytes,size,((double)recbytes/size)*100);
	else
	  sprintf(outbuf,recvbuf,0,0,100.0);
	if ( ! BE_QUIET ) printf("\r%s",outbuf);
	fflush(stdout);
      }
    }
    gettimeofday(&tvi,NULL);
    if ( BE_VERBOSE ) printf("\nTransfer finished");
    else if ( ! BE_QUIET ) printf("\n");
    if(wrong==1) {
      if ( BE_VERBOSE ) printf("\n");
      fprintf( stderr, "Error while transfer\nI have received wrong datas!\n" );
      fprintf( stderr, "Please repeat transmission!\n" );
    } else if(wrong==2) {
      if ( BE_VERBOSE ) printf("\n");
      fprintf( stderr, "Error while transfer\nThe Amiga reports data loss!\n" );
      fprintf( stderr, "Please repeat transmission!\n" );
    } else if ( wrong == 4 ) {
      ret = -1;
    } else {
      if(getfile>0) {
	secs=tvi.tv_sec-tvs.tv_sec;
	usecs=tvi.tv_usec-tvs.tv_usec;
	if(usecs<0) {
	  secs--;
	  usecs+=1000000;
	}
	d1=size;
	d1*=1000000;
	d1/=(secs*1000000.0+usecs);
	d1/=1024;
	if ( BE_VERBOSE ) printf(", file received with %.2g KBytes/s\n",d1);

	tfl.files++;
	tfl.bytes += size;
	tfl.tvs.tv_sec += secs;
	tfl.tvs.tv_usec += usecs;
	if ( tfl.tvs.tv_usec >= 1000000 ) {
	  tfl.tvs.tv_sec++;
	  tfl.tvs.tv_usec -= 1000000;
	}
      }
    }
    close_incoming_file();
    
    /* File is stored in the current dir when basedir is NULL
     * File is stored relative to current dir in basedir when basedir starts NOT with "/"
     * File is stored in basedir when basedir starts with "/"
     */

    /* First get the cwd */
    cwdsize=1024;
    cwd=(char*)malloc(cwdsize);
    if(cwd!=NULL) {
      while(getcwd(cwd,cwdsize)==NULL) {
	if(errno==ERANGE) {
	  free(cwd);
	  cwdsize*=2;
	  cwd=(char*)malloc(cwdsize);
	  if(cwd==NULL) break;
	} else {
	  free(cwd);
	  cwd=NULL;
	}
      }
    }
    
    /* now check all 3 cases */
    
    if ( basedir == NULL ) {
      
      /* just in the cwd */
      
      if(cwd!=NULL) {
	if(strlen(cwd)<2) {
	  tstr=(char*)malloc(strlen(cwd)+strlen(infile.name)+1);
	  if(tstr!=NULL) sprintf(tstr,"%s%s",cwd,infile.name);
	} else {
	  tstr=(char*)malloc(strlen(cwd)+1+strlen(infile.name)+1);
	  if(tstr!=NULL) sprintf(tstr,"%s/%s",cwd,infile.name);
	}
      }
    } else {
      
      /* at least relative to basedir */
      
      if ( basedir[0] != '/' ) {
	
	/* basedir is relative */
	
	if ( cwd[strlen( cwd ) - 1] == '/' ) {
	  tstr = (char*) malloc( strlen( cwd ) + strlen( basedir ) + strlen( infile.name ) + 1 );
	  if ( tstr != NULL ) sprintf( tstr, "%s%s%s", cwd, basedir, infile.name );
	} else {
	  tstr = (char*) malloc( strlen( cwd ) + 1 + strlen( basedir ) + strlen( infile.name ) + 1 );
	  if(tstr!=NULL) sprintf( tstr, "%s/%s%s", cwd, basedir, infile.name );
	}
      } else {
	tstr = (char*) malloc( strlen( basedir ) + strlen( infile.name ) + 1 );
	if ( tstr != NULL ) sprintf( tstr, "%s%s", basedir, infile.name );
      }
    }

    
    if(tstr!=NULL) {
      if ( BE_VERBOSE ) printf("Incoming file is stored at: %s\n",tstr);
      if ( wrong ) fprintf( stderr, "Wrong file is at %s\n", tstr );

      /* Apply mode
       * mode is the unix number but to be sure:
       */
      tm = 0;
      if ( mode & 0400 ) tm |= S_IRUSR;
      if ( mode & 0200 ) tm |= S_IWUSR;
      if ( mode & 0100 ) tm |= S_IXUSR;
      if ( mode & 0040 ) tm |= S_IRGRP;
      if ( mode & 0020 ) tm |= S_IWGRP;
      if ( mode & 0010 ) tm |= S_IXGRP;
      if ( mode & 0004 ) tm |= S_IROTH;
      if ( mode & 0002 ) tm |= S_IWOTH;
      if ( mode & 0001 ) tm |= S_IXOTH;
      
      if ( ! ignore_prot )
	if ( chmod( tstr, tm ) != 0 ) {
	  fprintf( stderr, "Can't restore file protection to %s\n", tstr );
      }

      free(tstr);
    }
    
    if ( cwd != NULL) free(cwd);
    free(infile.name);
  }
  free(inarg);
  return ret;
}

int sendfile(const char *name, int mode)
{
  struct timeval tvs,tvo;
  int secs,usecs;
  int outchecksum,inchecksum,checksum,wrong,tc;
  double d1;
  int sendfile;
  int sendbytes,ts,count;
  char outbuf[128],sendbuf[128];
  int pos=0;
  char *outbasename=NULL,*tstr;

  apc_command_t com;
  int ret=0;

  if(init_outgoing_file(name)!=0) {
    fprintf( stderr, "can't open file %s!\n", name );
    return 1;
  } else {
    sendfile=1;
    if ( ! BE_QUIET ) printf("sending file \"%s\", size %d\n",name,outfile.size);
  }
  checksum=0;
  putint(outblock,APC_COMMAND_FILETRANSFER);

  putint(outblock+4,outfile.size);

  outbasename=strrchr(name,'/');
  if(outbasename==NULL) outbasename=strdup(name);
  else {
    tstr=outbasename+1;
    outbasename=strdup(tstr);
  }
  putint(outblock+8,min(strlen(outbasename),TRANSFERBLOCKSIZE/2-16));
  strncpy(outblock+12,outbasename,TRANSFERBLOCKSIZE/2-16);
  free(outbasename);
  putint(outblock+TRANSFERBLOCKSIZE/2-4,mode);

  transferblock();

  com = (apc_command_t) getint( inblock );
  if ( com != APC_COMMAND_ABORT ) {

    /* preparing progress display */
    sprintf(outbuf,"%d",outfile.size);
    sprintf(sendbuf,"SEND: %%%dd/%%%dd (%%6.2f%%%%)",strlen(outbuf),strlen(outbuf));
    pos=6+strlen(outbuf)+1+strlen(outbuf)+2+6+1+1;
    
    inchecksum=outchecksum=0;
    sendbytes=0;
    wrong=0;
    
    /* now synchronize with other side and check state */
    if(recvcommand(&com,NULL,NULL)==0) {
      if(com==APC_COMMAND_NOP) {
	outchecksum=0;
	sendbytes=0;
	wrong=0;
	gettimeofday(&tvs,NULL);
	for(count=0;sendfile==1;count++) {
	  ts=readtobuffer(outblock,TRANSFERBLOCKSIZE/2-4,&outchecksum);
	  sendbytes+=ts;
	  if ( ts < 1 ) sendfile=2;
	  if ( ts < 0 ) wrong = 3;

	  if(wrong==0) putint(outblock+TRANSFERBLOCKSIZE/2-4,outchecksum);
	  else putint(outblock+TRANSFERBLOCKSIZE/2-4,0xffffffff);

	  if ( ( sendfile == 1 ) || ( wrong != 0 ) ) {
	    transferblock();
	    tc=getint(inblock+TRANSFERBLOCKSIZE-4);
	    if(tc==0xffffffff) wrong=2;
            else if ( tc == 0xffffffee ) wrong = 4; /* abort */
	  }
	  if(wrong!=0) break;

	  if ( ( count == 19 ) || ( sendfile != 1 ) ) {
	    count=0;
	    if ( outfile.size )
	      sprintf(outbuf,sendbuf,sendbytes,outfile.size,((double)sendbytes/outfile.size)*100);
	    else
	      sprintf(outbuf,sendbuf,0,0,100.0);
	    if ( ! BE_QUIET ) printf("\r%s",outbuf);
	    fflush(stdout);
	  }
	}
	gettimeofday(&tvo,NULL);
	if ( BE_VERBOSE ) printf("\nTransfer finished");
	else if ( ! BE_QUIET ) printf("\n");
	if(wrong==1) {
	  if ( BE_VERBOSE ) printf("\n");
	  fprintf( stderr, "Error while transfer\nI have received wrong datas!\n" );
	  fprintf( stderr, "Please repeat transmission!\n" );
	  fprintf( stderr, "Wrong file was %s\n", name );
	} else if(wrong==2) {
	  if ( BE_VERBOSE ) printf("\n");
	  fprintf( stderr, "Error while transfer\nThe Amiga reports data loss!\n" );
	  fprintf( stderr, "Please repeat transmission!\n" );
	  fprintf( stderr, "Wrong file was %s\n", name );
	} else if(wrong==3) {
	  if ( BE_VERBOSE ) printf("\n");
	  fprintf( stderr, "Error while reading file\n" );
	  fprintf( stderr, "Wrong file was %s\n", name );
        } else if ( wrong == 4 ) {
          ret = -1;
	} else {
	  secs=tvo.tv_sec-tvs.tv_sec;
	  usecs=tvo.tv_usec-tvs.tv_usec;
	  if(usecs<0) {
	    secs--;
	    usecs+=1000000;
	  }
	  d1=outfile.size;
	  d1*=1000000;
	  d1/=(secs*1000000.0+usecs);
	  d1/=1024;
	  if ( BE_VERBOSE ) printf(", file sended with %.2g KBytes/s\n",d1);

	  tfl.files++;
	  tfl.bytes += outfile.size;
	  tfl.tvs.tv_sec += secs;
	  tfl.tvs.tv_usec += usecs;
	  if ( tfl.tvs.tv_usec >= 1000000 ) {
	    tfl.tvs.tv_sec++;
	    tfl.tvs.tv_usec -= 1000000;
	  }
	}
      } else {
	if(com==APC_COMMAND_SKIPFILE) {
	  if ( ! BE_QUIET ) printf("Skipping file!\n");
	} else if(com==APC_COMMAND_ABORT) {
	  ret=-1;
	} else {
	  fprintf( stderr, "Wrong command %d\n", com );
	  ret=1;
	}
      }
    } else {
      fprintf( stderr, "Error while receiving command!\n" );
      ret=1;
    }
  } else {
    ret = -1;
  }
  close_outgoing_file();
  free(outfile.name);
  return ret;
}

int sendcommand(apc_command_t com,const char *arg, int mode)
{
  if(com==APC_COMMAND_FILETRANSFER) {
    return sendfile(arg, mode);
  } else if(com==APC_COMMAND_ERROR) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_ENTERDIR) {
    if( (strlen(arg)+1+4) <= (TRANSFERBLOCKSIZE/2-4) ) {
      putint(outblock,com);
      strncpy(outblock+4,arg,TRANSFERBLOCKSIZE/2-4-4);
      putint(outblock+TRANSFERBLOCKSIZE/2-4,mode);
      transferblock();
    } else {
      fprintf( stderr, "Too long dirname!\n" );
      return 1;
    }
  } else if(com==APC_COMMAND_LEAVEDIR) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_FINISHED) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_NOP) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_ABORT) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_SKIPFILE) {
    putint(outblock,com);
    transferblock();
  } else {
    fprintf( stderr, "Unknown command %d to send!\n", com );
    return 1;
  }
  return 0;
}
