#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <string.h>
#include <assert.h>

/**
 * @file unity.h
 */

#define UNITY_INTERNAL 1

#include "writers.h"
#include "unit-definitions.h"
#include "function-definitions.h"
#include "known-syntaxes.h"


/* Support writing formatted text to a fixed-size buffer. */
static Buf* bufinit(Buf* B, char* buf, const int buflen)
{
    B->buf = buf;
    B->len = buflen;
    B->nwritten = 0;
    return B;
}
static Buf* bufprintf(Buf* B, char* fmt, ...)
{
    va_list ap;

    assert(B->buf != NULL && B->nwritten <= B->len);

    va_start(ap, fmt);
    B->nwritten += vsnprintf(B->buf + B->nwritten,
                             B->len - B->nwritten,
                             fmt,
                             ap);
    va_end(ap);

    assert(B->nwritten <= B->len);

    return B;
}

static Buf* write_number(Buf* B, float f, int brackets_float, int brackets_negative, int brackets_positive)
{
    if (f == floorf(f)) {        /* no fractional part */
        int i = (int)roundf(f);
        int brackets = (i < 0 && brackets_negative) || (i >= 0 && brackets_positive);
        bufprintf(B, (brackets ? "(%d)" : "%d"), i);
    } else {
        int brackets = brackets_float || (f < 0.0 && brackets_negative) || (f >= 0.0 && brackets_positive);
        bufprintf(B, (brackets ? "(%g)" : "%g"), f);
    }
    return B;
}

static const char* prefix_power_to_string10(int prefixPower) {
    const char* rval;
    switch (prefixPower) {
      case -24: rval = "y"; break;
      case -21: rval = "z"; break;
      case -18: rval = "a"; break;
      case -15: rval = "f"; break;
      case -12: rval = "p"; break;
      case  -9: rval = "n"; break;
      case  -6: rval = "u"; break;
      case  -3: rval = "m"; break;
      case  -2: rval = "c"; break;
      case  -1: rval = "d"; break;
      case   0: rval = "";  break;
      case   1: rval = "da";break;
      case   2: rval = "h"; break;
      case   3: rval = "k"; break;
      case   6: rval = "M"; break;
      case   9: rval = "G"; break;
      case  12: rval = "T"; break;
      case  15: rval = "P"; break;
      case  18: rval = "E"; break;
      case  21: rval = "Z"; break;
      case  24: rval = "Y"; break;
      default:
        // This should be impossible, since it's only
        // unitylib:u_prefix_to_power10 which sets these values
        assert(0);
    }
    return rval;
}

static const char* prefix_power_to_string2(int prefixPower) 
{
    const char* rval;
    switch (prefixPower) {
      case 10: rval = "Ki"; break;
      case 20: rval = "Mi"; break;
      case 30: rval = "Gi"; break;
      case 40: rval = "Ti"; break;
      case 50: rval = "Pi"; break;
      case 60: rval = "Ei"; break;
      case 70: rval = "Zi"; break;
      case 80: rval = "Yi"; break;
      default:
        // This should be impossible, since it's only
        // unitylib:u_prefix_to_power2 which sets these values
        assert(0);
    }
    return rval;
}        

static const char* prefix_power_to_string(SimpleUnit u) 
{
    if (u.base10_p) {
        return prefix_power_to_string10(u.prefix_power);
    } else {
        return prefix_power_to_string2(u.prefix_power);
    }
}

static const char* prefix_power_to_string_latex10(int prefixPower) 
{
    const char* rval;
    switch (prefixPower) {
      case -24: rval = "\\yocto "; break;
      case -21: rval = "\\zepto "; break;
      case -18: rval = "\\atto ";  break;
      case -15: rval = "\\femto "; break;
      case -12: rval = "\\pico ";  break;
      case  -9: rval = "\\nano ";  break;
      case  -6: rval = "\\micro "; break;
      case  -3: rval = "\\milli "; break;
      case  -2: rval = "\\centi "; break;
      case  -1: rval = "\\deci ";  break;
      case   0: rval = "";         break;
      case   1: rval = "\\deca ";  break;
      case   2: rval = "\\hecto "; break;
      case   3: rval = "\\kilo ";  break;
      case   6: rval = "\\mega ";  break;
      case   9: rval = "\\giga ";  break;
      case  12: rval = "\\tera ";  break;
      case  15: rval = "\\peta ";  break;
      case  18: rval = "\\exa ";   break;
      case  21: rval = "\\zetta "; break;
      case  24: rval = "\\yotta "; break;
      default:
        // This should be impossible, since it's only
        // unitylib:u_prefix_to_power10 which sets these values
        assert(0);
    }
    return rval;
}

static const char* prefix_power_to_string_latex2(int prefixPower) 
{
    const char* rval;
    switch (prefixPower) {
      case 10: rval = "\\kibi "; break;
      case 20: rval = "\\mebi "; break;
      case 30: rval = "\\gibi "; break;
      case 40: rval = "\\tebi "; break;
      case 50: rval = "\\pebi "; break;
      case 60: rval = "\\exbi "; break;
      case 70: rval = "\\zebi "; break;
      case 80: rval = "\\yobi "; break;
      default:
        // This should be impossible, since it's only
        // unitylib:u_prefix_to_power2 which sets these values
        assert(0);
    }
    return rval;
}

static const char* prefix_power_to_string_latex(SimpleUnit u) 
{
    if (u.base10_p) {
        return prefix_power_to_string_latex10(u.prefix_power);
    } else {
        return prefix_power_to_string_latex2(u.prefix_power);
    }
}

static const char* find_base_unit(const Unit* u, UnitySyntax syntax) 
{
    assert(u->type == simple_unit_type);

    if (u->u.base_unit_string != NULL) {
        return u->u.base_unit_string;
    } else {
        const UnitRep* rep;
        const UnitDef* ud = u->u.base_unit_def;
        
        assert(ud != NULL);

        rep = u_get_unit_representation(ud, syntax);
        if (rep == NULL) {
            // This was a known unit in the syntax in which this unit
            // was originally parsed, but it's not a known unit in
            // this syntax.  So go through the list of syntaxes to find
            // one which does recognise it, and use that syntax's
            // symbol for it.  The result will be an unrecognised
            // symbol in the syntax 'syntax'.
            rep = u_get_unit_representation_any(ud);
#if 0
            int i;
            for (i=0; i<U_NSYNTAXES; i++) {
                /*
                fprintf(stderr, "find_base_unit: i=%d: u->type=%d, u->u.def=%s (idx=%d : %s)\n",
                        i,
                        u->type,
                        u->u.base_unit_def->uri,
                        u->u.base_unit_def->_idx,
                        u->u.base_unit_def->name);
                */
                rep = u_get_unit_representation(ud, i);
                if (rep != NULL) {
                    break;
                }
            }
#endif
            // this came from _some_ UnitRep, so we must have found
            // one in this search.
            assert(rep != NULL); 
        }
        assert(rep->symbol != NULL); // all UnitReps have non-NULL symbols

        return rep->symbol;
    }
}

static const char* find_base_unit_latex(const Unit* u)
{
    const char* unit_string;
    if (u->u.base_unit_string != NULL) {
        unit_string = u->u.base_unit_string;
    } else {
        const UnitDef* def = u->u.base_unit_def;
        assert(def != NULL);
        if (def->latex_form == NULL) {
            unit_string = find_base_unit(u, UNITY_SYNTAX_FITS);
        } else {
            unit_string = def->latex_form;
        }
    }
    assert(unit_string != NULL);
    return unit_string;
}

Buf* print_function(Buf* B,
                    const Unit* u,
                    const int syntax,
                    writef_ptr unit_writer)
{
    static const FunctionDef* log_function = NULL;
    int do_cds;

    if (log_function == NULL) {
        log_function = unity_get_functiondef_from_string("log", UNITY_SYNTAX_FITS);
    }

    /* we must have been called because u is a function-application type */
    assert(u->type == function_application_type);

    do_cds = (syntax == UNITY_SYNTAX_CDS && u->f.definition == log_function);

    if (do_cds) {
        bufprintf(B, "[");
    } else if (u->f.definition == NULL) {
        if (syntax == UNITY_SYNTAX_LATEX) {
            bufprintf(B, "\\mathop{\\mathrm{%s}}(", u->f.name);
        } else if (u->is_quoted_unit) {
            bufprintf(B, "'%s'(", u->f.name);
        } else {
            bufprintf(B, "%s(", u->f.name);
        }
    } else if (syntax == UNITY_SYNTAX_LATEX) {
        bufprintf(B, "%s(", u->f.definition->latex_form);
    } else {
        const char* abbrev = unity_get_function_abbreviation(u->f.definition, syntax);
        if (abbrev == NULL) {
            bufprintf(B, "\"%s\"(", u->f.definition->description);
        } else {
            bufprintf(B, "%s(", abbrev);
        }
    }

    // Now write the operand into the buffer.  This requires creating
    // another Buf object (not pretty)
    {
        Buf subbuf;
        bufinit(&subbuf,
                B->buf + B->nwritten,
                B->len - B->nwritten);
        unit_writer(&subbuf, &(u->f.operand));
        B->nwritten += subbuf.nwritten;
        assert(B->nwritten <= B->len);
    }

    if (do_cds) {
        bufprintf(B, "]");
    } else {
        bufprintf(B, ")");
    }

    if (u->power != 1.0) {
        bufprintf(B, "^%g", u->power);
    }

    return B;
}

Buf* write_fits(Buf* B, const UnitExpression* ue)
{
    int first = 1;
    const Unit* u;

    if (ue->log_factor != 0.0) {
        bufprintf(B, "10^");
        write_number(B, ue->log_factor, 1, 0, 0);
        first = 0;
    }
    for (u=ue->unit_sequence; u!=NULL; u=u->next) {
        if (first) {
            first = 0;
        } else {
            bufprintf(B, " ");
        }
        switch(u->type) {
          case simple_unit_type:
            bufprintf(B, "%s%s",
                      prefix_power_to_string(u->u),
                      find_base_unit(u, UNITY_SYNTAX_FITS));
            break;

          case function_application_type:
            print_function(B, u, UNITY_SYNTAX_FITS, write_fits);
            break;

          default:
            assert(0);
        }
        if (u->power != 1.0) {
            write_number(B, u->power, 1, 0, 0);
        }
    }
    return B;                   /* success return */
}

Buf* write_vounits(Buf* B, const UnitExpression* ue)
{
    int first = 1;
    const Unit* u;

    if (ue->log_factor == 0.0) {
        // do nothing
    } else if (ue->log_factor == 1.0) {
        // special-case this
        bufprintf(B, "10");
    } else {
        bufprintf(B, "%g", pow(10, ue->log_factor));
    }
    
    for (u=ue->unit_sequence; u!=NULL; u=u->next) {
        if (first) {
            first = 0;
        } else {
            bufprintf(B, ".");
        }

        switch(u->type) {
          case simple_unit_type:
            if (u->u.base_unit_string && u->is_quoted_unit) {
                // special case
                bufprintf(B, "%s'%s'",
                          prefix_power_to_string(u->u),
                          find_base_unit(u, UNITY_SYNTAX_VOUNITS));
            } else {
                bufprintf(B, "%s%s",
                          prefix_power_to_string(u->u),
                          find_base_unit(u, UNITY_SYNTAX_VOUNITS));
            }
            break;

          case function_application_type:
            print_function(B, u, UNITY_SYNTAX_VOUNITS, write_vounits);
            break;

          default:
            assert(0);
        }
        if (u->power != 1.0) {
            bufprintf(B, "**");
            write_number(B, u->power, 1, 0, 0);
        }
    }
    return B;                   /* success return */
}

Buf* write_ogip(Buf* B, const UnitExpression* ue)
{
    int first = 1;
    const Unit* u;

    if (ue->log_factor != 0.0) {
        bufprintf(B, "10**");
        write_number(B, ue->log_factor, 0, 1, 1);
        first = 0;
    }
    for (u=ue->unit_sequence; u!=NULL; u=u->next) {
        float e = u->power;
        if (first) {
            first = 0;
        } else {
            bufprintf(B, " ");
        }

        switch(u->type) {
          case simple_unit_type:
            {
                const char* base_unit_string = find_base_unit(u, UNITY_SYNTAX_OGIP);
                if (e == 1.0) {
                    bufprintf(B, "%s%s",
                              prefix_power_to_string(u->u),
                              base_unit_string);
                } else if (e < 0.0) {
                    bufprintf(B, "/%s%s",
                              prefix_power_to_string(u->u),
                              base_unit_string);
                    if (e != -1.0) {
                        bufprintf(B, "**");
                        write_number(B, -e, 0, 1, 1);
                    }
                } else {
                    // e is positive and not 1.0
                    bufprintf(B, "%s%s**", prefix_power_to_string(u->u), base_unit_string);
                    write_number(B, e, 0, 1, 1);
                }
            }
            
            break;

          case function_application_type:
            print_function(B, u, UNITY_SYNTAX_OGIP, write_ogip);
            break;

          default:
            assert(0);
        }
    }
    return B;                   /* success return */
}

Buf* write_cds(Buf* B, const UnitExpression* ue)
{
    int first = 1;
    const Unit* u;

    if (ue->log_factor == 0.0) {
        // do nothing
    } else if (ue->log_factor > -2.0 && ue->log_factor < 2.0) {
        // there's no requirement that we avoid the x10+nn notation
        // for 'small' factors, but it seems neat to do so
        bufprintf(B, "%g", pow(10, ue->log_factor), 0, 0, 0);
    } else {
        double low = floor(ue->log_factor);
        double logmantissa = ue->log_factor - low;
        if (logmantissa == 0.0) {
            // round number
            bufprintf(B, "10%+.0f", low);
        } else {
            bufprintf(B, "%gx10%+.0f", pow(10, logmantissa), low);
        }
    }

    for (u=ue->unit_sequence; u!=NULL; u=u->next) {

        switch(u->type) {
          case simple_unit_type:
            {
                const char* base_unit_string = find_base_unit(u, UNITY_SYNTAX_CDS);
                float e = u->power;
                  
                if (e == 1.0) {
                    bufprintf(B, "%s%s%s", (first ? "" : "."),
                              prefix_power_to_string(u->u),
                              base_unit_string);
                } else if (e < 0.0) {
                    bufprintf(B, "/%s%s",
                              prefix_power_to_string(u->u),
                              base_unit_string);
                    if (e != -1.0) {
                        write_number(B, -e, 0, 0, 0);
                    }
                } else {
                    // e is positive and not 1.0
                    bufprintf(B, "%s%s%s", (first ? "" : "."),
                              prefix_power_to_string(u->u),
                              base_unit_string);
                    write_number(B, e, 0, 0, 0);
                }
            }
            break;

          case function_application_type:
            print_function(B, u, UNITY_SYNTAX_CDS, write_cds);
            break;

          default:
            assert(0);
        }
        
        first = 0;
    }
    return B;                   /* success return */
}

Buf* write_latex(Buf* B, const UnitExpression* ue)
{
    int first = 1;
    const Unit* u;
    
    if (ue->log_factor == 0.0) {
        bufprintf(B, "\\si{");
    } else if (ue->log_factor == 1.0) { /* special-case this */
        bufprintf(B, "\\si{10");
    } else {
        bufprintf(B, "\\si{%g", pow(10, ue->log_factor));
    }
    for (u=ue->unit_sequence; u!=NULL; u=u->next) {
        switch (u->type) {
          case simple_unit_type:
            {
                const char* base_unit_string = find_base_unit_latex(u);
                float e = u->power;
                if (e == 1.0) {
                    bufprintf(B, "%s%s%s", (first ? "" : "."),
                              prefix_power_to_string_latex(u->u),
                              base_unit_string);
                } else if (e < 0.0) {
                    bufprintf(B, "\\per %s%s",
                              prefix_power_to_string_latex(u->u),
                              base_unit_string);
                    if (e != -1.0) {
                        bufprintf(B, "^{");
                        write_number(B, -e, 0, 0, 0);
                        bufprintf(B, "}");
                    }
                } else {
                    // e is positive and not 1.0
                    bufprintf(B, "%s%s%s", (first ? "" : "."),
                              prefix_power_to_string_latex(u->u),
                              base_unit_string);
                    bufprintf(B, "^{");
                    write_number(B, e, 0, 0, 0);
                    bufprintf(B, "}");
                }
            }
            break;

          case function_application_type:
            {
                bufprintf(B, "%s\\text{\\ensuremath{", (first ? "" : "."));
                print_function(B, u, UNITY_SYNTAX_LATEX, write_latex);
                bufprintf(B, "}}");
            }
            break;

          default:
            assert(0);
        }

        first = 0;
    }

    bufprintf(B, "}");

    return B;                   /* success return */
}


Buf* write_debugging(Buf* B, const UnitExpression* unit_expression)
{
    int first = 1;
    static int suppress_unit_factor = 0;
    Unit** sorted_units;
    int n_sorted_units, i;

    if (unit_expression->log_factor == 0.0 && suppress_unit_factor) {
        // nothing
    } else if (floor(unit_expression->log_factor) == unit_expression->log_factor) {
        bufprintf(B, "%g", unit_expression->log_factor);
        first = 0;
    } else {
        bufprintf(B, "%.3f", unit_expression->log_factor);
        first = 0;
    }
    sorted_units = u_get_sorted_unit_sequence((UnitExpression*)unit_expression,
                                              &n_sorted_units);
    for (i=0; i<n_sorted_units; i++) {
        const Unit* u = sorted_units[i];
        switch(u->type) {
          case simple_unit_type:
            if (first) {
                first = 0;
            } else {
                bufprintf(B, " ");
            }
            bufprintf(B, "%d%s/%s/%g",
                      u->u.prefix_power,
                      u->u.base10_p ? "" : "b",
                      find_base_unit(u, UNITY_SYNTAX_VOUNITS),
                      u->power);
            break;

          case function_application_type:
            if (!first)
                bufprintf(B, " ");
            suppress_unit_factor = 1;
            print_function(B, u, UNITY_SYNTAX_FITS, write_debugging);
            suppress_unit_factor = 0;
            break;

          default:
            assert(0);
        }
    }
    return B;
}

static struct {
    const char* name;
    UnitySyntax stx;
    writef_ptr func;
} writer_list[] = {
    { "vounits", UNITY_SYNTAX_VOUNITS, &write_vounits },
    { "fits",    UNITY_SYNTAX_FITS,    &write_fits },
    { "ogip",    UNITY_SYNTAX_OGIP,    &write_ogip },
    { "cds",     UNITY_SYNTAX_CDS,     &write_cds },
    { "latex",   UNITY_SYNTAX_LATEX,   &write_latex },
    { "debug",   UNITY_SYNTAX_DEBUG,   &write_debugging },
};
static int writer_list_size = sizeof(writer_list)/sizeof(writer_list[0]);

/**
 * Write a unit-expression to the given buffer.  The available formats
 * are those returned by the function {@link unity_parser_names}.
 *
 * @param buf the buffer to be written to
 * @param buflen the length of the input buffer
 * @param ue the unit-expression to be written
 * @param stx one of the available formats
 * @return buf on success, or NULL on error
 */
char* unity_write_formatted(char* buf,
                            int buflen,
                            const UnitExpression* ue,
                            UnitySyntax stx)
{
    writef_ptr f = NULL;
    int i;
    char* rval;
    Buf bufBuf;

    if (ue == NULL) {
        return NULL;            /* JUMP OUT */
    }

    for (i=0; i<writer_list_size; i++) {
        /* if (strcmp(format, writer_list[i].name) == 0) { */
        if (stx == writer_list[i].stx) {
            f = writer_list[i].func;
            break;
        }
    }
    if (f == NULL) {
        fprintf(stderr, "Can't find a writer of format %d\n", stx);
        rval = NULL;
    } else {
        f(bufinit(&bufBuf, buf, buflen), ue);
        rval = buf;
    }
    return rval;
}

/**
 * Return the available formatter names.  If this is impossible for some
 * reason (which can really only be memory exhaustion, which should
 * never happen), print an error message and return NULL.
 * The returned list is statically allocated, and should not be freed
 * by the caller.
 * @return a NULL-terminated list of pointers to formatter names
 */
const char** unity_formatter_names(void)
{
    static const char** res = NULL;
    if (res == NULL) {
        // initialise
        int i;
        if ((res = (const char**)malloc(sizeof(char*)*(writer_list_size+1))) == NULL) {
            fprintf(stderr, "Can't allocate space for unity_format_names\n");
            return NULL;
        }
        for (i=0; i<writer_list_size; i++) {
            res[i] = writer_list[i].name;
        }
        res[writer_list_size] = NULL; /* terminate the list */
    }
    return res;
}
