#include "nsCLABSignString.h"
#include "nsMemory.h"

#include "nsICMSMessage.h"
#include "nsICMSEncoder.h"
#include "nsIHash.h"
#include "nsIX509Cert.h"
#include "nsIUserCertPicker.h"

#include "nsReadableUtils.h"
#include "nsComponentManagerUtils.h"

#include "nsIDOMClassInfo.h"

#define BLOCK_LENGTH  10000


static void myCallback(void *arg,const char *buf,unsigned long len)
{
  nsCLABSignString * s = (nsCLABSignString *) arg;
  s->Eat(buf,len);
}

//NS_IMPL_ISUPPORTS1(nsCLABSignString, nsICLABSignString)

NS_INTERFACE_MAP_BEGIN(nsCLABSignString)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
  NS_INTERFACE_MAP_ENTRY(nsICLABSignString)
  NS_INTERFACE_MAP_ENTRY_EXTERNAL_DOM_CLASSINFO(CLABSignString)
NS_INTERFACE_MAP_END

NS_IMPL_ADDREF(nsCLABSignString)
NS_IMPL_RELEASE(nsCLABSignString)

nsCLABSignString::nsCLABSignString()
{
  /* member initializers and constructor code */
  buffer = (unsigned char*) nsMemory::Alloc(BLOCK_LENGTH);
  len = BLOCK_LENGTH;
  p = buffer;
}

nsCLABSignString::~nsCLABSignString()
{
  /* destructor code */
  unsigned char *buff = buffer;
  buffer = nsnull;
  if (buff){
    nsMemory::Free(buff);
  }
}

/* void signString (in nsIInterfaceRequestor ctx, in string toBeSigned, in unsigned long toBeSignedLen, out unsigned long status, out unsigned long signatureLen, [size_is (signatureLen), retval] out string signature); */
NS_IMETHODIMP nsCLABSignString::SignString(nsIInterfaceRequestor *ctx, const char *toBeSigned, PRUint32 toBeSignedLen, PRUint32 *status, PRUint32 *signatureLen, char **signature)
{
  //    return NS_ERROR_NOT_IMPLEMENTED;
  nsresult rv;

  nsCOMPtr<nsIX509Cert> cert;
  PRUint32 hLen;
  unsigned char *hValue = nsnull;
  PRUint32 hashLen;
  PRBool canceled;

  *status = nsICLABSignString::STATUS_OK;

  
  nsCOMPtr<nsIUserCertPicker> picker = do_CreateInstance(NS_CERT_PICKER_CONTRACTID);

  rv = picker->PickByUsage(ctx,(PRUnichar *)nsnull,4,0,0,&canceled,getter_AddRefs(cert));
  if (NS_FAILED(rv)) goto FAIL;

  if (!canceled) {

    nsCOMPtr<nsIHash> aHash = do_CreateInstance(NS_HASH_CONTRACTID);

    rv = aHash->Create(nsIHash::HASH_AlgSHA1);
    if (NS_FAILED(rv)) goto FAIL;
  
    rv = aHash->ResultLen(nsIHash::HASH_AlgSHA1,&hashLen);
    if (NS_FAILED(rv)) goto FAIL;
    
    hValue = (unsigned char *)nsMemory::Alloc(hashLen);
    
    rv = aHash->Begin();
    if (NS_FAILED(rv)) goto FAIL;
    rv = aHash->Update((unsigned char *)toBeSigned,(unsigned int)toBeSignedLen);
    if (NS_FAILED(rv)) goto FAIL;
    rv = aHash->End(hValue,&hLen,hashLen);
    if (NS_FAILED(rv)) goto FAIL;
    

    nsCOMPtr<nsICMSMessage> msg = do_CreateInstance(NS_CMSMESSAGE_CONTRACTID);

    rv = msg->CreateSigned(cert,cert,(unsigned char*)hValue,(unsigned int)hLen);
    if (NS_FAILED(rv)) goto FAIL;
    
    p = buffer;
    eating_error = 0;
    eating_ns_error = NS_OK;
    
    nsCOMPtr<nsICMSEncoder> enc = do_CreateInstance(NS_CMSENCODER_CONTRACTID);

    rv = enc->Start(msg,myCallback,(void*) this);
    if (NS_FAILED(rv)) goto FAIL;
    rv = enc->Finish();
    if (NS_FAILED(rv)) goto FAIL;
    
    if (eating_error){
      rv = eating_ns_error;
      goto FAIL;
    }
    
    *signature = (char *)nsMemory::Clone(buffer,p-buffer);
    *signatureLen = (p-buffer);
  } else {
    rv = NS_ERROR_ABORT;
    *status = nsICLABSignString::STATUS_CANCELLED_BY_USER;
    *signature = (char *) nsMemory::Alloc(1);
    *signatureLen = 0;
  }
 FAIL:
  if (hValue) nsMemory::Free(hValue);
  return rv;
}

void nsCLABSignString::Eat(const char *buf,unsigned long len)
{
  unsigned char *aux;
  unsigned char *aux2;

  if (eating_error)
    return;

  if (!buffer){
    eating_error = 1;
    eating_ns_error = NS_ERROR_NULL_POINTER;
    return;
  }

  if ((p - buffer) + len > this->len){
    // realloc
    unsigned char *new_buf = (unsigned char*)nsMemory::Alloc(this->len + (len > BLOCK_LENGTH ? len:BLOCK_LENGTH));
    aux = buffer;
    aux2 = new_buf;

    while (aux != p) {
      *aux2++ = *aux++;
    }
    nsMemory::Free(buffer);
    buffer = new_buf;
    p = aux2;
    this->len += BLOCK_LENGTH;
  }
  
  aux = (unsigned char *)buf;
  aux2 = (unsigned char*)buf + len;
  while (aux != aux2) *p++ = *aux++;

}
