#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

#include "sass_interface.h"
#include "context.hpp"
#include "inspect.hpp"

#ifndef SASS_ERROR_HANDLING
#include "error_handling.hpp"
#endif

#include <iostream>
#include <sstream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>

extern "C" {
  using namespace std;

  sass_context* sass_new_context()
  { return (sass_context*) calloc(1, sizeof(sass_context)); }

  void free_string_array(char ** arr, int num) {
    if(!arr)
        return;

    for(int i = 0; i < num; i++) {
      free(arr[i]);
    }

    free(arr);
  }

  void sass_free_context(sass_context* ctx)
  {
    if (ctx->output_string) free(ctx->output_string);
    if (ctx->source_map_string) free(ctx->source_map_string);
    if (ctx->error_message) free(ctx->error_message);

    free_string_array(ctx->included_files, ctx->num_included_files);

    free(ctx);
  }

  sass_file_context* sass_new_file_context()
  { return (sass_file_context*) calloc(1, sizeof(sass_file_context)); }

  void sass_free_file_context(sass_file_context* ctx)
  {
    if (ctx->output_string)     free(ctx->output_string);
    if (ctx->source_map_string) free(ctx->source_map_string);
    if (ctx->error_message)     free(ctx->error_message);

    free_string_array(ctx->included_files, ctx->num_included_files);

    free(ctx);
  }

  sass_folder_context* sass_new_folder_context()
  { return (sass_folder_context*) calloc(1, sizeof(sass_folder_context)); }

  void sass_free_folder_context(sass_folder_context* ctx)
  {
    free_string_array(ctx->included_files, ctx->num_included_files);
    free(ctx);
  }

  void copy_strings(const std::vector<std::string>& strings, char*** array, int* n) {
    int num = strings.size();
    char** arr = (char**) malloc(sizeof(char*)* num);

    for(int i = 0; i < num; i++) {
      arr[i] = (char*) malloc(sizeof(char) * strings[i].size() + 1);
      std::copy(strings[i].begin(), strings[i].end(), arr[i]);
      arr[i][strings[i].size()] = '\0';
    }

    *array = arr;
    *n = num;
  }

  // helper for safe access to c_ctx
  const char* safe_str (const char* str) {
    return str == NULL ? "" : str;
  }

  int sass_compile(sass_context* c_ctx)
  {
    using namespace Sass;
    try {
      string input_path = safe_str(c_ctx->input_path);
      int lastindex = input_path.find_last_of(".");
      string output_path;
      if (!c_ctx->output_path) {
        if (input_path != "") {
          output_path = (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css";
        }
      }
      else {
          output_path = c_ctx->output_path;
      }
      Context cpp_ctx(
        Context::Data().source_c_str(c_ctx->source_string)
                       .output_path(output_path)
                       .output_style((Output_Style) c_ctx->options.output_style)
                       .is_indented_syntax_src(c_ctx->options.is_indented_syntax_src)
                       .source_comments(c_ctx->options.source_comments)
                       .source_map_file(safe_str(c_ctx->options.source_map_file))
                       .omit_source_map_url(c_ctx->options.omit_source_map_url)
                       .image_path(safe_str(c_ctx->options.image_path))
                       .include_paths_c_str(c_ctx->options.include_paths)
                       .include_paths_array(0)
                       .include_paths(vector<string>())
                       .precision(c_ctx->options.precision ? c_ctx->options.precision : 5)
      );
      if (c_ctx->c_functions) {
        struct Sass_C_Function_Descriptor* this_func_data = c_ctx->c_functions;
        while (this_func_data->signature && this_func_data->function) {
          cpp_ctx.c_functions.push_back(*this_func_data);
          ++this_func_data;
        }
      }
      // by checking c_ctx->input_path, implementors can pass in an empty string
      c_ctx->output_string = c_ctx->input_path ? cpp_ctx.compile_string(input_path) :
                                                 cpp_ctx.compile_string();
      c_ctx->source_map_string = cpp_ctx.generate_source_map();
      c_ctx->error_message = 0;
      c_ctx->error_status = 0;

      copy_strings(cpp_ctx.get_included_files(), &c_ctx->included_files, &c_ctx->num_included_files);
    }
    catch (Error& e) {
      stringstream msg_stream;
      msg_stream << e.path << ":" << e.position.line << ": " << e.message << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch(bad_alloc& ba) {
      stringstream msg_stream;
      msg_stream << "Unable to allocate memory: " << ba.what() << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch (std::exception& e) {
      stringstream msg_stream;
      msg_stream << "Error: " << e.what() << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch (string& e) {
      stringstream msg_stream;
      msg_stream << "Error: " << e << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch (...) {
      // couldn't find the specified file in the include paths; report an error
      stringstream msg_stream;
      msg_stream << "Unknown error occurred" << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    return 0;
  }

  int sass_compile_file(sass_file_context* c_ctx)
  {
    using namespace Sass;
    try {
      string input_path = safe_str(c_ctx->input_path);
      int lastindex = input_path.find_last_of(".");
      string output_path;
      if (!c_ctx->output_path) {
          output_path = (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css";
      }
      else {
          output_path = c_ctx->output_path;
      }
      Context cpp_ctx(
        Context::Data().entry_point(input_path)
                       .output_path(output_path)
                       .output_style((Output_Style) c_ctx->options.output_style)
                       .source_comments(c_ctx->options.source_comments)
                       .source_map_file(safe_str(c_ctx->options.source_map_file))
                       .omit_source_map_url(c_ctx->options.omit_source_map_url)
                       .image_path(safe_str(c_ctx->options.image_path))
                       .include_paths_c_str(c_ctx->options.include_paths)
                       .include_paths_array(0)
                       .include_paths(vector<string>())
                       .precision(c_ctx->options.precision ? c_ctx->options.precision : 5)
      );
      if (c_ctx->c_functions) {
        struct Sass_C_Function_Descriptor* this_func_data = c_ctx->c_functions;
        while (this_func_data->signature && this_func_data->function) {
          cpp_ctx.c_functions.push_back(*this_func_data);
          ++this_func_data;
        }
      }
      c_ctx->output_string = cpp_ctx.compile_file();
      c_ctx->source_map_string = cpp_ctx.generate_source_map();
      c_ctx->error_message = 0;
      c_ctx->error_status = 0;

      copy_strings(cpp_ctx.get_included_files(), &c_ctx->included_files, &c_ctx->num_included_files);
    }
    catch (Error& e) {
      stringstream msg_stream;
      msg_stream << e.path << ":" << e.position.line << ": " << e.message << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch(bad_alloc& ba) {
      stringstream msg_stream;
      msg_stream << "Unable to allocate memory: " << ba.what() << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch (std::exception& e) {
      stringstream msg_stream;
      msg_stream << "Error: " << e.what() << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch (string& e) {
      stringstream msg_stream;
      msg_stream << "Error: " << e << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    catch (...) {
      // couldn't find the specified file in the include paths; report an error
      stringstream msg_stream;
      msg_stream << "Unknown error occurred" << endl;
      c_ctx->error_message = strdup(msg_stream.str().c_str());
      c_ctx->error_status = 1;
      c_ctx->output_string = 0;
      c_ctx->source_map_string = 0;
    }
    return 0;
  }

  int sass_compile_folder(sass_folder_context* c_ctx)
  {
    return 1;
  }

  const char* quote (const char *str, const char quotemark) {
    return Sass::quote(str, quotemark).c_str();
  }

  const char* unquote (const char *str) {
    return Sass::unquote(str).c_str();
  }

}
