#include <cstdlib>
#include <cstring>
#include <vector>
#include <sstream>

#ifndef SASS
#include "sass.h"
#endif

#include "context.hpp"

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

extern "C" {
  using namespace std;

  struct Sass_Context* make_sass_context()
  { return (Sass_Context*) calloc(1, sizeof(Sass_Context)); }

  void free_sass_context(struct Sass_Context* ctx)
  {
    if (ctx->output_string) free(ctx->output_string);
    if (ctx->error_message) free(ctx->error_message);
    free(ctx);
  }

  namespace Sass {
    enum Sass_Source { FILE_SOURCE, STRING_SOURCE };

    static void compile_sass(struct Sass_Context* c_ctx,
                             Sass::Sass_Source src_option)
    {
      using namespace Sass;
      try {
        Context cpp_ctx(
          Context::Data().source_c_str        (c_ctx->input_string)

                         .entry_point         (c_ctx->input_path ?
                                               c_ctx->input_path :
                                               "")

                         .output_style        ((Output_Style)
                                               c_ctx->output_style)

                         .source_comments     (c_ctx->source_comments)
                         .source_map_file     (c_ctx->source_map_file)
                         .omit_source_map_url (c_ctx->omit_source_map_url)

                         .image_path          (c_ctx->image_path ?
                                               c_ctx->image_path :
                                               "")

                         .output_path         (c_ctx->output_path ?
                                               c_ctx->output_path :
                                               "")

                         .include_paths_c_str (c_ctx->include_paths_string)
                         .include_paths_array (/*c_ctx->include_paths_array*/0)
                         .include_paths       (vector<string>())
                         .precision           (c_ctx->precision ? c_ctx->precision : 5)
        );
        if (src_option == FILE_SOURCE) cpp_ctx.compile_file();
        else                           cpp_ctx.compile_string();
        c_ctx->error_message = 0;
        c_ctx->error_status = 0;
      }
      catch (Error& e) {
        stringstream msg_stream;
        msg_stream << e.path << ":" << e.position.line << ": error: " << e.message << endl;
        string msg(msg_stream.str());
        char* msg_str = (char*) malloc(msg.size() + 1);
        strcpy(msg_str, msg.c_str());
        c_ctx->error_status = 1;
        c_ctx->output_string = 0;
        c_ctx->error_message = msg_str;
      }
      catch (bad_alloc& ba) {
        stringstream msg_stream;
        msg_stream << "Unable to allocate memory: " << ba.what() << endl;
        string msg(msg_stream.str());
        char* msg_str = (char*) malloc(msg.size() + 1);
        strcpy(msg_str, msg.c_str());
        c_ctx->error_status = 1;
        c_ctx->output_string = 0;
        c_ctx->error_message = msg_str;
      }
    }
  }

  void compile_sass_file(struct Sass_Context* c_ctx)
  { Sass::compile_sass(c_ctx, Sass::FILE_SOURCE); }

  void compile_sass_string(struct Sass_Context* c_ctx)
  { Sass::compile_sass(c_ctx, Sass::STRING_SOURCE); }


  union Sass_Value make_sass_boolean(int val)
  {
    union Sass_Value v;
    v.boolean.tag = SASS_BOOLEAN;
    v.boolean.value = val;
    return v;
  }

  union Sass_Value make_sass_number(double val, const char* unit)
  {
    union Sass_Value v;
    v.number.tag = SASS_NUMBER;
    v.number.value = val;
    v.number.unit = strdup(unit);
    return v;
  }

  union Sass_Value make_sass_color(double r, double g, double b, double a)
  {
    union Sass_Value v;
    v.color.tag = SASS_COLOR;
    v.color.r = r;
    v.color.g = g;
    v.color.b = b;
    v.color.a = a;
    return v;
  }

  union Sass_Value make_sass_string(const char* val)
  {
    union Sass_Value v;
    v.string.tag = SASS_STRING;
    v.string.value = strdup(val);
    return v;
  }

  union Sass_Value make_sass_list(size_t len, enum Sass_Separator sep)
  {
    union Sass_Value v;
    v.list.tag = SASS_LIST;
    v.list.length = len;
    v.list.separator = sep;
    v.list.values = (union Sass_Value*) malloc(sizeof(union Sass_Value)*len);
    return v;
  }

  union Sass_Value make_sass_map(size_t len)
  {
    union Sass_Value v;
    v.map.tag = SASS_MAP;
    v.map.length = len;
    v.map.pairs = (struct Sass_KeyValuePair*) malloc(sizeof(struct Sass_KeyValuePair)*len);
    return v;
  }

  union Sass_Value make_sass_null()
  {
    union Sass_Value v;
    v.null.tag = SASS_NULL;
    return v;
  }

  union Sass_Value make_sass_error(const char* msg)
  {
    union Sass_Value v;
    v.error.tag = SASS_ERROR;
    v.error.message = strdup(msg);
    return v;
  }

  // make_sass_* may allocated additional memory
  void free_sass_value(const union Sass_Value val) {

    int i;
    switch(val.unknown.tag) {
        // case SASS_NULL: {
        // }   break;
        // case SASS_BOOLEAN: {
        // }   break;
        case SASS_NUMBER: {
                free(val.number.unit);
        }   break;
        // case SASS_COLOR: {
        // }   break;
        case SASS_STRING: {
                free(val.string.value);
        }   break;
        case SASS_LIST: {
                for (i=0; i<val.list.length; i++) {
                    free_sass_value(val.list.values[i]);
                }
                free(val.list.values);
        }   break;
        case SASS_MAP: {
                for (i=0; i<val.map.length; i++) {
                    free_sass_value(val.map.pairs[i].key);
                    free_sass_value(val.map.pairs[i].value);
                }
                free(val.map.pairs);
        }   break;
        case SASS_ERROR: {
                free(val.error.message);
        }   break;
    }

  }

}
