/*  httpget.cpp
    A simple http client

    13.08.2001: tk, initial implementation, based on Beck
                    example code.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.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. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files ----------
//
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "inet/httpget.h"
#include "inet/base64.h"
#include "inet/dns.h"
#include "inet/url.h"
#include "clib/rtos.h"
#include "clib/tcpip.h"
#include "mpbuf.h"
#include "misc.h"
#include "vs1001.h"

/**
 * Gets a string from a socket.
 * sockgets reads characters from a socket into the string s.
 * The function stops reading when it reads either n-1 characters
 * or a newline character whichever comes first. sockgets retains
 * the newline character at the end of s. A null byte is appended
 * to s to mark the end of the string.
 *
 * \bug  shouldn't that be "n>0" ?!
 *
 * \param s   pointer to string
 * \param n   maximum length of string
 * \param sd  socket to read from
 * \return    pointer to received string,
 *            or NULL if an error occured.
 */
char *sockgets(char *s, int n, int sd)
{
  int   error, rcvd;
  char *ptr = s;

  while (n>1) {
    rcvd = recv(sd, ptr, 1, MSG_TIMEOUT, 10000L, &error);
    if (error != 0 || rcvd != 1)
      return NULL;

    if (*ptr++ == '\n')
      break;

    n--;
  }
  *ptr = '\0';
  assert(ptr < s+n);

  return s;
}


/**
 * Outputs a string to a socket.
 * fputs copies the null-terminated string s to the given output
 * stream; it does not append a newline character and the terminating
 * null character is not copied.
 *
 * \param s   pointer to string
 * \param sd  socket to write to
 * \return    On successful completion, puts sockputs returns a
 *            nonnegative value. Otherwise, it returns a value of EOF
 */
int sockputs(const char *s, int sd)
{
  int len, error, sent;

  len = strlen(s);
  while (len>0) {
    sent = send(sd, (char*)s, len, 0, &error);
    if (error!=0 || sent<0)
      break;
    s   += sent;
    len -= sent;
  }
  assert(len>=0);

  return (len==0) ? 1 : EOF;
}


/**
 * Connect to a HTTP server.
 *
 * \param  urlString  server URL to connect to
 * \param  error      variable receving the error code
 * \return socket descriptor for the connection,
 *         or 0 if an error occured.
 */
unsigned  http_connect(const char *urlString, int *error)
{
  unsigned long  ipnumber, ttl;
  char     ipaddr[16];
  unsigned socket = 0;

  URL  *url = parse_url(urlString);
  if (!url->port)
    url->port = DEFAULT_HTTP_PORT;

  printf("connecting to %s..\n", url->host);
  if (inet_addr(url->host, &ipnumber)) {
    int ret = gethostbyname(NULL, url->host, &ttl, &ipnumber, ipaddr);
    if (!ret) {
      printf("  can't resolve host address!\n");
      // No route to host error.. not exactly, but close ;)
      //
      *error = 265;
      goto Return;
    } else printf("  resolved to %s.\n", ipaddr);
  } else {
    strncpy(ipaddr, url->host, sizeof(ipaddr));
  }

  // Send request to server -----
  //
  char  request[512], *r = request;  // worst case !
  r += sprintf(r, "GET /%s HTTP/1.0\r\n", url->path ? url->path : "");
  r += sprintf(r, "Host: %s:%u\r\n", url->host, url->port);
  r += sprintf(r, "User-Agent: mp3v2 client 1.1, www.kreapc.de\r\n");
  r += sprintf(r, "Connection: Keep-Alive\r\n");
//  r += sprintf(r, "Icy-MetaData: 1\r\n");

  if (url->user) {
    char *authcode = NULL;
    char str[64], *s = str;

    s += sprintf(s, "%s", url->user);
    if (url->pass)
      s += sprintf(s, ":%s", url->pass);
    assert((s-str)<sizeof(str));
    authcode = base64encode(str, strlen(str), NULL);

    if (authcode) {
      r += sprintf(r, "Authorization: Basic %s\r\n", authcode);
      free(authcode);
    }
  }
  r += sprintf(r, "\r\n");
  assert((r-request)<sizeof(request));

  printf("  opening connection.. ");
  socket = tcp_connect(ipaddr, 0, url->port, error);
  if (*error) {
    printf("error %d.\n", *error);
    goto Return;
  } else printf("ok.\n");

  printf("  sending request.. ");
  printf("\n%s\n", request);

  if (!sockputs(request, socket)) {
    printf( "error while sending request\n");
    goto Return;
  } else printf("ok.\n");


Return:
  if (url)
    free_url(url);
  return  socket;
}


void  getMetaData(int sd)
{
  int   error, rcvd;
  char  metalen;
  char  metadata[512];

  rcvd = recv(sd, &metalen, 1, MSG_TIMEOUT, 10000L, &error);
  if (error!=0 || rcvd!=1)
    return;

  if (metalen) {
    printf("%d bytes: ", metalen*16);
    rcvd = recv(sd, metadata, metalen*16, MSG_TIMEOUT, 10000L, &error);
    if (error!=0)
      return;
    printf("%s\n", metadata);
  }

}


void  http_get(const char *urlString)
{
  int       sd, error;
  unsigned  rcvd;
  char      header[512];

  int       tcp_delay = 0;
  SetSocketOption options = {
    IP_PROTOTCP_LEVEL, TCP_DELAY_ACK,
    (char*)&tcp_delay, sizeof(tcp_delay)        
  };

  sd = http_connect(urlString, &error);
  if (error)
    goto Return;
          
  // set TCP_DELAY_ACK option
  setsockopt(sd, &options, &error);
  printf("setsockopt returned %x\n", error);

  printf("receiving header.. ");
  while (1) {
    sockgets(header, sizeof(header), sd);
    printf("%s", header);
    if (!strcmp(header, "\r\n"))
      break;
  }

  // set threshold to 50% to handle slow servers nicely
  //
  BUF_SetThreshold(BUF_GetSize()/2);
  BUF_Play();

  while (!kbhit() && !error) {
//    LCD_SetBrightness(((float)BUF_GetCount()/BUF_GetSize())*255);

    // fill buffer
    //
    if (BUF_GetFreeBytes() > 8192) {
      char *buf1;
      long  len = 8192, len1;

      while (len>0) {
        BUF_Lock(-1, len, &buf1, &len1, 0, 0 );
        rcvd = recv(sd, buf1, len1, MSG_TIMEOUT, 10000L, &error);
        if (error || rcvd == 0)
          break;
        BUF_Unlock(rcvd);
        len -= rcvd;
      }
//      getMetaData(sd);

      printf(".");
    }
    // update display
    //
/*    cleardevice();
    gotoxy(0,0);
    gprintf(ProgressBar(BUF_GetCount(), 0, BUF_GetSize()));
    LCD_Update();  */

    // be nice to other apps..
    //
    RTX_Sleep_Time(100);
  }
  BUF_Clear();
  getch();

Return:
  if (sd)
    closesocket(sd, &error);
  return;
}
