/*
    Copyright (C) 2012 Modelon AB

    This program is free software: you can redistribute it and/or modify
    it under the terms of the BSD style license.

     This program 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
    FMILIB_License.txt file for more details.

    You should have received a copy of the FMILIB_License.txt file
    along with this program. If not, contact Modelon AB <http://www.modelon.com>.
*/

/** \file fmi2_xml_model_structure.c
*  \brief Implementation for the model structure interface.
*/
#include <string.h>
#include <stdio.h>

#include "fmi2_xml_parser.h"
#include "fmi2_xml_model_structure_impl.h"
#include "fmi2_xml_model_description_impl.h"

static const char * module = "FMI2XML";

fmi2_xml_model_structure_t* fmi2_xml_allocate_model_structure(jm_callbacks* cb) {
	fmi2_xml_model_structure_t* ms = (fmi2_xml_model_structure_t*)(cb->calloc(1, sizeof(fmi2_xml_model_structure_t)));
	if(!ms) return 0;

	jm_vector_init(jm_voidp)(&ms->outputs,0,cb);
	jm_vector_init(jm_voidp)(&ms->derivatives,0,cb);
	jm_vector_init(jm_voidp)(&ms->discreteStates,0,cb);
	jm_vector_init(jm_voidp)(&ms->initialUnknowns,0,cb);

	ms->isValidFlag = 1;

    ms->outputDeps = fmi2_xml_allocate_dependencies(cb);
    ms->derivativeDeps = fmi2_xml_allocate_dependencies(cb);
    ms->discreteStateDeps = fmi2_xml_allocate_dependencies(cb);
    ms->initialUnknownDeps = fmi2_xml_allocate_dependencies(cb);

	if(!ms->outputDeps || !ms->derivativeDeps || !ms->discreteStateDeps || !ms->initialUnknownDeps) {
		fmi2_xml_free_model_structure(ms);
		return 0;
	}

	return ms;
}

void fmi2_xml_free_model_structure(fmi2_xml_model_structure_t* ms) {
	jm_callbacks* cb;
	if(!ms) return;
	cb = ms->outputs.callbacks;

	jm_vector_free_data(jm_voidp)(&ms->outputs);
	jm_vector_free_data(jm_voidp)(&ms->derivatives);
	jm_vector_free_data(jm_voidp)(&ms->discreteStates);
	jm_vector_free_data(jm_voidp)(&ms->initialUnknowns);

    fmi2_xml_free_dependencies(ms->outputDeps);
    fmi2_xml_free_dependencies(ms->derivativeDeps);
    fmi2_xml_free_dependencies(ms->discreteStateDeps);
    fmi2_xml_free_dependencies(ms->initialUnknownDeps);
	cb->free(ms);
}

jm_vector(jm_voidp)* fmi2_xml_get_outputs(fmi2_xml_model_structure_t* ms) {
	return &ms->outputs;
}

jm_vector(jm_voidp)* fmi2_xml_get_derivatives(fmi2_xml_model_structure_t* ms){
	return &ms->derivatives;
}

jm_vector(jm_voidp)* fmi2_xml_get_discrete_states(fmi2_xml_model_structure_t* ms){
	return &ms->discreteStates;
}

jm_vector(jm_voidp)* fmi2_xml_get_initial_unknowns(fmi2_xml_model_structure_t* ms){
	return &ms->initialUnknowns;
}


void fmi2_xml_get_dependencies(fmi2_xml_dependencies_t* dep, size_t** startIndex, size_t** dependency, char** factorKind){
    if(dep) {
        if (jm_vector_get_size(size_t)(&dep->dependencyIndex) == 0) {
            *startIndex = NULL;
            *dependency = NULL;
            *factorKind = NULL;
        } else {
            *startIndex = jm_vector_get_itemp(size_t)(&dep->startIndex, 0);
            *dependency = jm_vector_get_itemp(size_t)(&dep->dependencyIndex, 0);
            *factorKind = jm_vector_get_itemp(char)(&dep->dependencyFactorKind, 0);
        }
    }
    else {
        *startIndex = 0;
    }
}

void fmi2_xml_get_outputs_dependencies(fmi2_xml_model_structure_t* ms,
                                           size_t** startIndex, size_t** dependency, char** factorKind) {
    fmi2_xml_get_dependencies(ms->outputDeps, startIndex, dependency, factorKind);
}

void fmi2_xml_get_derivatives_dependencies(fmi2_xml_model_structure_t* ms,
                                           size_t** startIndex, size_t** dependency, char** factorKind) {
    fmi2_xml_get_dependencies(ms->derivativeDeps, startIndex, dependency, factorKind);
}

void fmi2_xml_get_discrete_states_dependencies(fmi2_xml_model_structure_t* ms,
                                           size_t** startIndex, size_t** dependency, char** factorKind) {
    fmi2_xml_get_dependencies(ms->discreteStateDeps, startIndex, dependency, factorKind);
}

void fmi2_xml_get_initial_unknowns_dependencies(fmi2_xml_model_structure_t* ms,
                                           size_t** startIndex, size_t** dependency, char** factorKind) {
    fmi2_xml_get_dependencies(ms->initialUnknownDeps, startIndex, dependency, factorKind);
}


fmi2_xml_dependencies_t* fmi2_xml_allocate_dependencies(jm_callbacks* cb) {
	fmi2_xml_dependencies_t* dep = (fmi2_xml_dependencies_t*)(cb->malloc(sizeof(fmi2_xml_dependencies_t)));
	if(!dep) return 0;
	jm_vector_init(size_t)(&dep->startIndex, 0, cb);
	jm_vector_push_back(size_t)(&dep->startIndex, 0);

	jm_vector_init(size_t)(&dep->dependencyIndex, 0, cb);
	jm_vector_init(char)(&dep->dependencyFactorKind, 0, cb);

	dep->isRowMajor = 1;

	return dep;
}

void fmi2_xml_zero_empty_dependencies(fmi2_xml_dependencies_t** pdep) {
	fmi2_xml_dependencies_t* dep =*pdep;
	size_t ndep = jm_vector_get_size(size_t)(&dep->dependencyIndex);
	size_t i;
	if(!dep) return;
	for(i = 0; i<ndep;i++) {
		if(jm_vector_get_item(size_t)(&dep->dependencyIndex, i)) break;
	}
	if(i == ndep) {
		fmi2_xml_free_dependencies(dep);
		*pdep = 0;
	}
}


void fmi2_xml_free_dependencies(fmi2_xml_dependencies_t* dep) {
    jm_callbacks* cb;
	if(!dep) return;
    cb = dep->startIndex.callbacks;
	jm_vector_free_data(size_t)(&dep->startIndex);

	jm_vector_free_data(size_t)(&dep->dependencyIndex);
	jm_vector_free_data(char)(&dep->dependencyFactorKind);
    cb->free(dep);
}


int fmi2_xml_check_model_structure(fmi2_xml_model_description_t* md) {
	fmi2_xml_model_structure_t* ms = md->modelStructure;

	if(!ms || !ms->isValidFlag) return 0;

	return ms->isValidFlag;
}

int fmi2_xml_handle_ModelStructure(fmi2_xml_parser_context_t *context, const char* data) {
    fmi2_xml_model_description_t* md = context->modelDescription;
    if(!data) {
		jm_log_verbose(context->callbacks, module,"Parsing XML element ModelStructure");
		/** allocate model structure */
		md->modelStructure = fmi2_xml_allocate_model_structure(md->callbacks);
		if(!md->modelStructure) {
				fmi2_xml_parse_fatal(context, module, "Could not allocate memory");
				return -1;
		}
    }
    else {
		/** make sure model structure information is consistent */

		if(!fmi2_xml_check_model_structure(md)) {
			fmi2_xml_parse_fatal(context, "Model structure is not valid due to detected errors. Cannot continue.");
			return -1;
		}
/*		md->numberOfContinuousStates = jm_vector_get_size(jm_voidp)(&md->modelStructure->states); */

    }
    return 0;
}


int fmi2_xml_handle_Outputs(fmi2_xml_parser_context_t *context, const char* data) {
    if (!data) {
        jm_log_verbose(context->callbacks, module, "Parsing XML element Outputs");
        /*  reset handles for the elements that are specific under Outputs */
/*        fmi2_xml_set_element_handle(context, "Unknown", FMI2_XML_ELM_ID(OutputUnknown));*/
        fmi2_xml_set_element_handle(context, "Unknown", FMI2_XML_ELM_ID(Unknown));
    }
    return 0;
}
int fmi2_xml_handle_Derivatives(fmi2_xml_parser_context_t *context, const char* data) {
    if (!data) {
        jm_log_verbose(context->callbacks, module, "Parsing XML element Derivatives");
        /*  reset handles for the elements that are specific under Derivatives */
        fmi2_xml_set_element_handle(context, "Unknown", FMI2_XML_ELM_ID(DerivativeUnknown));
    }
    else {
        fmi2_xml_model_description_t* md = context->modelDescription;
        fmi2_xml_model_structure_t* ms = md->modelStructure;
        /* count the number of continuous states as the number of <Unknown> elements under <Derivatives> */
        md->numberOfContinuousStates = jm_vector_get_size(jm_voidp)(&ms->derivatives);
    }
    return 0;
}
int fmi2_xml_handle_DiscreteStates(fmi2_xml_parser_context_t *context, const char* data) {
    if (!data) {
        jm_log_verbose(context->callbacks, module, "Parsing XML element DiscreteStates");
        /*  reset handles for the elements that are specific under DiscreteStates */
        fmi2_xml_set_element_handle(context, "Unknown", FMI2_XML_ELM_ID(DiscreteStateUnknown));
    }
    return 0;
}
int fmi2_xml_handle_InitialUnknowns(fmi2_xml_parser_context_t *context, const char* data) {
    if (!data) {
        jm_log_verbose(context->callbacks, module, "Parsing XML element InitialUnknowns");
        /*  reset handles for the elements that are specific under InitialUnknowns */
        fmi2_xml_set_element_handle(context, "Unknown", FMI2_XML_ELM_ID(InitialUnknown));
    }
    return 0;
}


int fmi2_xml_parse_dependencies(fmi2_xml_parser_context_t *context,
                                fmi2_xml_elm_enu_t parentElmID,
								fmi2_xml_dependencies_t* deps)
{
    fmi2_xml_model_description_t* md = context->modelDescription;
    fmi2_xml_model_structure_t* ms = md->modelStructure;

    const char* listInd;
    const char* listKind;
    size_t numDepInd = 0;
    size_t numDepKind = 0;
    size_t totNumDep = jm_vector_get_size(size_t)(&deps->dependencyIndex);

    /*  <xs:attribute name="dependencies">
            <xs:simpleType>
                <xs:list itemType="xs:unsignedInt"/>
            </xs:simpleType>
        </xs:attribute> */
    if(fmi2_xml_get_attr_str(context, fmi2_xml_elmID_Unknown, fmi_attr_id_dependencies, 0, &listInd)) {
        ms->isValidFlag = 0;
        return 0;
    }
    if(listInd) {
         const char* cur = listInd;
         int ind;
         while(*cur) {
             char ch = *cur;
             while((ch ==' ') || (ch == '\t') || (ch =='\n') || (ch == '\r')) {
                 cur++; ch = *cur;
                 if(!ch) break;
             }
             if(!ch) break;
             if(sscanf(cur, "%d", &ind) != 1) {
                 fmi2_xml_parse_error(context, "XML element 'Unknown': could not parse item %d in the list for attribute 'dependencies'",
                     numDepInd);
                ms->isValidFlag = 0;
                return 0;
             }
             if(ind < 1) {
                 fmi2_xml_parse_error(context, "XML element 'Unknown': item %d=%d is less than one in the list for attribute 'dependencies'",
                     numDepInd, ind);
                ms->isValidFlag = 0;
                return 0;
             }
             if(!jm_vector_push_back(size_t)(&deps->dependencyIndex, (size_t)ind)) {
                fmi2_xml_parse_fatal(context, "Could not allocate memory");
                return -1;
            }
             while((*cur >= '0') && (*cur <= '9')) cur++;
             numDepInd++;
         }
    }

    /*
        <xs:attribute name="dependenciesKind">
            <xs:simpleType>
                <xs:list>
                    <xs:simpleType>
                        <xs:restriction base="xs:normalizedString">
                            <xs:enumeration value="dependent"/>
                            <xs:enumeration value="constant"/>
                            <xs:enumeration value="fixed"/>
                            <xs:enumeration value="tunable"/>
                            <xs:enumeration value="discrete"/>
                        </xs:restriction>
                    </xs:simpleType>
                </xs:list>
            </xs:simpleType>
        </xs:attribute>
        */
    if(fmi2_xml_get_attr_str(context, fmi2_xml_elmID_Unknown, fmi_attr_id_dependenciesKind, 0, &listKind)) {
        ms->isValidFlag = 0;
        return 0;
    }
    if(listKind) {
         const char* cur = listKind;
         char kind;
         while(*cur) {
             char ch = *cur;
             while(ch && ((ch ==' ') || (ch == '\t') || (ch =='\n') || (ch == '\r'))) {
                 cur++; ch = *cur;
             }
             if(!ch) break;
             if(strncmp("dependent", cur, 9) == 0) {
                 kind = fmi2_dependency_factor_kind_dependent;
                 cur+=9;
             }
             else if(strncmp("constant", cur, 8) == 0) {
                 kind = fmi2_dependency_factor_kind_constant;
                 cur+=8;
             }
             else if(strncmp("fixed", cur, 5) == 0) {
                 kind = fmi2_dependency_factor_kind_fixed;
                 cur+=5;
             }
             else if(strncmp("tunable", cur, 7) == 0) {
                 kind = fmi2_dependency_factor_kind_tunable;
                 cur+=7;
             }
             else if(strncmp("discrete", cur, 8) == 0) {
                  kind = fmi2_dependency_factor_kind_discrete;
                 cur+=8;
             }
             else {
                 fmi2_xml_parse_error(context, "XML element 'Unknown': could not parse item %d in the list for attribute 'dependenciesKind'",
                     numDepKind);
                 ms->isValidFlag = 0;
                 return 0;
             }
             if (parentElmID == fmi2_xml_elmID_InitialUnknowns) {
                if (kind == fmi2_dependency_factor_kind_fixed) {
                    fmi2_xml_parse_error(context, "XML element 'Unknown' within 'InitialUnknowns': 'fixed' is not allowed in list for attribute 'dependenciesKind'; setting to 'dependent'");
                    kind = fmi2_dependency_factor_kind_dependent;
                }
                else if (!(kind == fmi2_dependency_factor_kind_dependent || kind == fmi2_dependency_factor_kind_constant)) {
                    fmi2_xml_parse_error(context, "XML element 'Unknown' within 'InitialUnknowns': only 'dependent' and 'constant' allowed in list for attribute 'dependenciesKind'");
                    ms->isValidFlag = 0;
                    return 0;
                }
             }
             if(!jm_vector_push_back(char)(&deps->dependencyFactorKind, kind)) {
                fmi2_xml_parse_fatal(context, "Could not allocate memory");
                return -1;
            }
             numDepKind++;
         }
    }
    if(listInd && listKind) {
        /* both lists are present - the number of items must match */
        if(numDepInd != numDepKind) {
            fmi2_xml_parse_error(context, "XML element 'Unknown': different number of items (%u and %u) in the lists for 'dependencies' and 'dependenciesKind'",
                                 numDepInd, numDepKind);
            ms->isValidFlag = 0;
            return 0;
        }
    }
    else if(listInd) {
        /* only Dependencies are present, set all kinds to dependent */
        char kind = fmi2_dependency_factor_kind_dependent;
        if(jm_vector_reserve(char)(&deps->dependencyFactorKind,totNumDep + numDepInd) < totNumDep + numDepInd) {
            fmi2_xml_parse_fatal(context, "Could not allocate memory");
            return -1;
        }
        for(;numDepKind < numDepInd; numDepKind++)
            jm_vector_push_back(char)(&deps->dependencyFactorKind, kind);
    }
    else if(listKind) {
        fmi2_xml_parse_error(context, "XML element 'Unknown': if `dependenciesKind` attribute is present then the `dependencies` attribute must be present also.");
        ms->isValidFlag = 0;
        return 0;
    }
    else {
        /* Dependencies are not provided. Put zero index/dependent to indicate that full row must be considered. */
        numDepInd = numDepKind = 1;
        if(!jm_vector_push_back(char)(&deps->dependencyFactorKind, fmi2_dependency_factor_kind_dependent) ||
         !jm_vector_push_back(size_t)(&deps->dependencyIndex, 0)
          ) {
            fmi2_xml_parse_fatal(context, "Could not allocate memory");
            return -1;
        }
    }
    if(!jm_vector_push_back(size_t)(&deps->startIndex, totNumDep + numDepInd)) {
        fmi2_xml_parse_fatal(context, "Could not allocate memory");
        return -1;
    }

    return 0;
}


int fmi2_xml_parse_unknown(fmi2_xml_parser_context_t *context,
                           fmi2_xml_elm_enu_t parentElmID,
                           jm_vector(jm_voidp) *destVarList,
                           fmi2_xml_dependencies_t* deps)
{
    fmi2_xml_model_description_t* md = context->modelDescription;
    fmi2_xml_model_structure_t* ms = md->modelStructure;

    unsigned int index;
    fmi2_xml_variable_t* variable;

    /* <xs:attribute name="index" type="xs:unsignedInt" use="required"> */
    if (fmi2_xml_set_attr_uint(context, fmi2_xml_elmID_Unknown, fmi_attr_id_index, 1, &index, 0)) return -1;
    index--; /* Convert from one- to zero-based indexing */

    /* Ok to just check upper bound since index is unsigned. */
    if (index >= jm_vector_get_size(jm_voidp)(md->variablesOrigOrder)) {
        fmi2_xml_parse_error(context, "The index attribute must have a value between 1 and the number of model variables.");
        ms->isValidFlag = 0;
        return -1;
    }
    variable = (fmi2_xml_variable_t*)jm_vector_get_item(jm_voidp)(md->variablesOrigOrder, index);

    if (!jm_vector_push_back(jm_voidp)(destVarList, variable)) {
        fmi2_xml_parse_fatal(context, "Could not allocate memory");
        ms->isValidFlag = 0;
        return -1;
    }

    return fmi2_xml_parse_dependencies(context, parentElmID, deps);
}


/*int fmi2_xml_handle_OutputUnknown(fmi2_xml_parser_context_t *context, const char* data) {*/
int fmi2_xml_handle_Unknown(fmi2_xml_parser_context_t *context, const char* data) {
    if(!data) {
        fmi2_xml_model_description_t* md = context->modelDescription;
        fmi2_xml_model_structure_t* ms = md->modelStructure;

        return fmi2_xml_parse_unknown(context, fmi2_xml_elmID_Outputs, &ms->outputs, ms->outputDeps);
    }
    else {
    }
    return 0;
}

int fmi2_xml_handle_DerivativeUnknown(fmi2_xml_parser_context_t *context, const char* data) {
    if(!data) {
        fmi2_xml_model_description_t* md = context->modelDescription;
        fmi2_xml_model_structure_t* ms = md->modelStructure;
        int status = fmi2_xml_parse_unknown(context, fmi2_xml_elmID_Derivatives, &ms->derivatives, ms->derivativeDeps);

        if (status) {
            return status;
        } else {
            fmi2_xml_real_variable_t *der = (fmi2_xml_real_variable_t*) jm_vector_get_last(jm_voidp)(&ms->derivatives);
            if (!fmi2_xml_get_real_variable_derivative_of(der)) {
                ms->isValidFlag = 0;
                fmi2_xml_parse_error(context,
                        "The state derivative '%s' does not specify the state variable that it is a derivative of.",
                        fmi2_xml_get_variable_name((fmi2_xml_variable_t *) der));
                return -1;
            }
        }
    }
    return 0;
}

int fmi2_xml_handle_DiscreteStateUnknown(fmi2_xml_parser_context_t *context, const char* data) {
    if(!data) {
        fmi2_xml_model_description_t* md = context->modelDescription;
        fmi2_xml_model_structure_t* ms = md->modelStructure;

        return fmi2_xml_parse_unknown(context, fmi2_xml_elmID_DiscreteStates, &ms->discreteStates, ms->discreteStateDeps);
    }
    else {
    }
    return 0;
}

int fmi2_xml_handle_InitialUnknown(fmi2_xml_parser_context_t *context, const char* data) {
    if(!data) {
        fmi2_xml_model_description_t* md = context->modelDescription;
        fmi2_xml_model_structure_t* ms = md->modelStructure;

        return fmi2_xml_parse_unknown(context, fmi2_xml_elmID_InitialUnknowns, &ms->initialUnknowns, ms->initialUnknownDeps);
    }
    else {
    }
    return 0;
}
