/*
 * Authors:
 * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
 * DeM Labs Ltd.   https://demlabs.net
 * Copyright  (c) 2017
 * All rights reserved.

 This file is part of DAP SDK the open source project

    DAP SDK 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 3 of the License, or
    (at your option) any later version.

    DAP SDK 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 any DAP SDK based project.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>

#ifndef _WIN32
#include <sys/types.h>
#include <sys/stat.h>
#else
#include <winsock2.h>
#include <windows.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <io.h>
#endif

#include <pthread.h>
#include <magic.h>

#include "dap_common.h"
#include "dap_events_socket.h"
#include "dap_http.h"
#include "dap_http_client.h"
#include "dap_http_folder.h"
#include "http_status_code.h"

typedef struct dap_http_url_proc_folder {
    char local_path[4096];
    magic_t mime_detector;
} dap_http_url_proc_folder_t;

#define URL_PROC_FOLDER(a) ((dap_http_url_proc_folder_t*) (a)->_inhertior )

typedef struct dap_http_file{
    FILE * fd;
    size_t position;
    char local_path[4096+2048+1];
    dap_http_client_t *client;
} dap_http_file_t;

#define DAP_HTTP_FILE(a) ((dap_http_file_t*) (a)->_inheritor )

void dap_http_folder_headers_read( dap_http_client_t *cl_ht, void *arg );
void dap_http_folder_headers_write( dap_http_client_t *cl_ht, void *arg );
void dap_http_folder_data_read( dap_http_client_t *cl_ht, void *arg );
void dap_http_folder_data_write( dap_http_client_t *cl_ht, void *arg );

#define LOG_TAG "dap_http_folder"

int dap_http_folder_init( )
{
    return 0;
}

void dap_http_folder_deinit( )
{

}


/**
 * @brief dap_http_folder_add Add folder for reading to the HTTP server
 * @param sh Server instance
 * @param url_path Beginning part of the URL
 * @param local_path Local path that will be read for
 */
int dap_http_folder_add( dap_http_t *sh, const char *url_path, const char *local_path )
{
  if ( !local_path ) {
    log_it( L_ERROR, "Directory Path parameter is empty!" );
    return -11;
  }

  log_it( L_DEBUG, "Checking url path %s", local_path );

#ifndef _WIN32
  DIR *dirptr = opendir( local_path );
  if ( dirptr == NULL ) {
    log_it( L_ERROR, "Directory Not Found!" );
    return -11;
  }
  else {
    closedir( dirptr );
  }
#else // WIN32

  DWORD attr = GetFileAttributesA( local_path );
  if ( attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY) ) {
    log_it( L_ERROR, "Directory Not Found!" );
    return -11;
  }

#endif

  log_it( L_NOTICE, "File service for %s => %s ", url_path, local_path );

  dap_http_url_proc_folder_t *up_folder = (dap_http_url_proc_folder_t *)calloc( 1, sizeof(dap_http_url_proc_folder_t) );
  strncpy( up_folder->local_path, local_path, sizeof(up_folder->local_path)-1 );

  up_folder->mime_detector = magic_open( MAGIC_SYMLINK | MAGIC_MIME | MAGIC_PRESERVE_ATIME );

  if ( up_folder->mime_detector == NULL) {
    log_it( L_CRITICAL,"Can't init MIME detection library" );
    free( up_folder );
    return -1;
  }

#ifndef _WIN32
  if( 0 != magic_load( up_folder->mime_detector, NULL) ) {
#else
  if( 0 != magic_load( up_folder->mime_detector, "data.mag" )  ) {
#endif

    log_it( L_CRITICAL, "Can't load MIME magic detection database" );
    magic_close( up_folder->mime_detector );
    free( up_folder );
    return -2;
  }

  dap_http_add_proc(  sh, 
                      url_path, 
                      up_folder, 
                      NULL,
                      NULL,
                      dap_http_folder_headers_read,
                      dap_http_folder_headers_write,
                      dap_http_folder_data_read,
                      dap_http_folder_data_write,
                      NULL );
    return 0;
}

/**
 * @brief dap_http_folder_headers_read Signal thats HTTP client is now going to output the data
 * @param cl_ht HTTP client instance
 * @param arg Not used
 */
void dap_http_folder_headers_read(dap_http_client_t * cl_ht, void * arg)
{
    (void) arg;
    cl_ht->state_write=DAP_HTTP_CLIENT_STATE_START;
    cl_ht->state_read=cl_ht->keep_alive?DAP_HTTP_CLIENT_STATE_START:DAP_HTTP_CLIENT_STATE_NONE;

    dap_events_socket_set_writable_unsafe(cl_ht->esocket,true);
    dap_events_socket_set_readable_unsafe(cl_ht->esocket, cl_ht->keep_alive);
}

#ifdef _WIN32
time_t FileTimeToUnixTime( FILETIME ft )
{
  ULARGE_INTEGER ull;
 
  ull.LowPart = ft.dwLowDateTime;
  ull.HighPart = ft.dwHighDateTime;
 
  return ull.QuadPart / 10000000ULL - 11644473600ULL;
}
#endif

/**
 * @brief dap_http_folder_headers Prepare response HTTP headers for file folder request
 * @param cl_ht HTTP client instane
 * @param arg Not used
 */
void dap_http_folder_headers_write( dap_http_client_t *cl_ht, void * arg)
{
  (void) arg;
  // Get specific data for folder URL processor
  dap_http_url_proc_folder_t * up_folder=(dap_http_url_proc_folder_t*) cl_ht->proc->_inheritor;

  // Init specific file response data for HTTP client instance
  cl_ht->_inheritor=DAP_NEW_Z(dap_http_file_t);

  dap_http_file_t* cl_ht_file=DAP_HTTP_FILE(cl_ht);
  cl_ht_file->client=cl_ht;

  // Produce local path for file to open
  dap_snprintf(cl_ht_file->local_path,sizeof(cl_ht_file->local_path),"%s/%s", up_folder->local_path, cl_ht->url_path );
  log_it(L_DEBUG, "Check %s file", cl_ht_file->local_path);

#ifndef _WIN32

  struct stat file_stat;

  if ( stat(cl_ht_file->local_path, &file_stat) != 0 ) 
    goto err;

  cl_ht->out_last_modified  = file_stat.st_mtime;
  cl_ht->out_content_length = file_stat.st_size;

#else

  FILETIME CreationTime;
  FILETIME LastAccessTime;
  FILETIME LastWriteTime;

  HANDLE fileh = CreateFileA( cl_ht_file->local_path, 
                              GENERIC_READ, 
                              FILE_SHARE_READ, 
                              NULL, 
                              OPEN_EXISTING, 
                              FILE_ATTRIBUTE_ARCHIVE, 
                              NULL 
                 );

  if ( fileh == INVALID_HANDLE_VALUE ) 
    goto err;

  GetFileTime( fileh,
               &CreationTime,
               &LastAccessTime,
               &LastWriteTime );

  cl_ht->out_last_modified  = FileTimeToUnixTime( LastWriteTime );
  cl_ht->out_content_length = GetFileSize( fileh, NULL );

  CloseHandle( fileh );

#endif

  cl_ht_file->fd = fopen( cl_ht_file->local_path, "rb" );

  if ( cl_ht_file->fd == NULL ) {
    log_it(L_ERROR, "Can't open %s: %s",cl_ht_file->local_path,strerror(errno));
    cl_ht->reply_status_code = Http_Status_NotFound;
    strncpy( cl_ht->reply_reason_phrase, "Not Found", sizeof(cl_ht->reply_reason_phrase)-1 );
  }
  else {
    cl_ht->reply_status_code = Http_Status_OK;
    strncpy( cl_ht->reply_reason_phrase,"OK",sizeof(cl_ht->reply_reason_phrase)-1 );

    const char *mime_type = magic_file( up_folder->mime_detector, cl_ht_file->local_path );
    if( mime_type ) {
      strncpy(cl_ht->out_content_type,mime_type,sizeof(cl_ht->out_content_type)-1);
      log_it( L_DEBUG, "MIME type detected: '%s'", mime_type );
    }
    else {
      cl_ht->reply_status_code=Http_Status_NotFound;
      cl_ht->esocket->flags |= DAP_SOCK_SIGNAL_CLOSE;
      log_it(L_WARNING,"Can't detect MIME type of %s file: %s",cl_ht_file->local_path,magic_error(up_folder->mime_detector));
    }
  }

  return;

err:

  log_it( L_WARNING, "Can't get file info: %s", strerror(errno) );
  cl_ht->reply_status_code = 404;
  strncpy( cl_ht->reply_reason_phrase, "Not Found", sizeof(cl_ht->reply_reason_phrase)-1 );

  return;
}

/**
 * @brief dap_http_folder_read HTTP client callback for reading function for the folder processing
 * @param cl_ht HTTP client instance
 * @param arg Pointer to int with return bytes number
 */
void dap_http_folder_data_read(dap_http_client_t * cl_ht, void * arg)
{
    int * bytes_return = (int*) arg; // Return number of read bytes
    //Do nothing
    *bytes_return=cl_ht->esocket->buf_in_size;
}

/**
 * @brief dap_http_folder_write HTTP client callback for writting function for the folder processing
 * @param cl_ht HTTP client instance
 * @param arg
 */
void dap_http_folder_data_write(dap_http_client_t * cl_ht, void * arg)
{
    (void) arg;
    dap_http_file_t * cl_ht_file= DAP_HTTP_FILE(cl_ht);
    cl_ht->esocket->buf_out_size=fread(cl_ht->esocket->buf_out,1,sizeof(cl_ht->esocket->buf_out),cl_ht_file->fd);
    cl_ht_file->position+=cl_ht->esocket->buf_out_size;
    dap_events_socket_set_writable_unsafe(cl_ht->esocket, true);

    if(feof(cl_ht_file->fd)!=0){
        log_it(L_INFO, "All the file %s is sent out",cl_ht_file->local_path);
        //strncat(cl_ht->client->buf_out+cl_ht->client->buf_out_size,"\r\n",sizeof(cl_ht->client->buf_out));
        fclose(cl_ht_file->fd);
        dap_events_socket_set_writable_unsafe(cl_ht->esocket,false);

        if ( !cl_ht->keep_alive )
            cl_ht->esocket->flags |= DAP_SOCK_SIGNAL_CLOSE;

        cl_ht->state_write=DAP_HTTP_CLIENT_STATE_NONE;
    }
}