let amf = require('amf-client-js')

let resolvedModel;
let parse = (from, url, callback) => {
    filePath = url;
    let model = { model: null, error: null, validates: false, validationMessages: [] };

    amf.AMF.init()
        .then(() => {
            
            let parser;
            if (from === "raml") {
                parser = amf.Core.parser("RAML 1.0", "application/yaml");
            } else if (from === "raml08") {
                parser = amf.Core.parser("RAML 0.8", "application/yaml");
            } else if (from === "oasjson") {
                parser = amf.Core.parser("OAS 2.0", "application/json");
            } else if (from === "oasyaml") {
                parser = amf.Core.parser("OAS 2.0", "application/yaml");
            } else if (from === "oas3json") {
                parser = amf.Core.parser("OAS 3.0", "application/json");
            } else if (from === "oas3yaml") {
                parser = amf.Core.parser("OAS 3.0", "application/yaml");
            } else {
                parser = amf.Core.parser("AMF Graph", "application/ld+json");
            }

            parser.parseFileAsync(url).then((amfModel) => {

                // this has all the types, securitySchemes, traits, resourceTypes, annotations, etc
                // let declares = amfModel.declares;
                // model.shapes = mapDeclaredShapes(declares);
                //mapShapesInReferences(amfModel, model);

                if (from === "raml") {
                    //resolvedModel = amf.AMF.resolveRaml10(amfModel);
                    var resolver = new amf.Resolver("RAML 1.0")
                    resolvedModel = resolver.resolve(amfModel, "editing")
                } else if (from === "raml08") {
                    //resolvedModel = amf.AMF.resolveRaml08(amfModel);
                    var resolver = new amf.Resolver("RAML 0.8")
                    resolvedModel = resolver.resolve(amfModel, "editing")
                } else if (from === "oasyaml" || from == "oasjson") {
                    //resolvedModel = amf.AMF.resolveOas20(amfModel);
                    var resolver = new amf.Resolver("OAS 2.0")
                    resolvedModel = resolver.resolve(amfModel, "editing")
                } else if (from === "oas3yaml" || from == "oas3json") {
                    //resolvedModel = amf.AMF.resolveOas20(amfModel);
                    var resolver = new amf.Resolver("OAS 3.0")
                    resolvedModel = resolver.resolve(amfModel, "editing")
                } else {
                    //resolvedModel = amf.AMF.resolveAmfGraph(amfModel);
                    var resolver = new amf.Resolver("AMF Graph") // TODO: check!!
                    resolvedModel = resolver.resolve(amfModel, "editing")
                }
 
                let declares = resolvedModel.declares;
                model.shapes = mapDeclaredShapes(declares);
                mapShapesInReferences(resolvedModel, model);
                model.model = mapModel(resolvedModel);

                // disable validation
                model.validates = true;
                model.validationMessages = [];
                return callback(model);

                let validationProfile;
                switch (from) {
                  case 'raml': validationProfile = amf.ProfileNames.RAML; break;
                  case 'raml08': validationProfile = amf.ProfileNames.RAML08; break;
                  case 'oasjson':
                  case 'oasyaml':
                    validationProfile = amf.ProfileNames.OAS;
                    break;
                  case 'oas3json':
                  case 'oas3yaml':
                    validationProfile = amf.ProfileNames.OAS30;
                    break;
                }
                amf.AMF.validate(amfModel, validationProfile).then((validation) => {
                    model.validates = validation.conforms;
                    model.validationMessages = mapValidationResults(validation.results);
                    // console.log(validation.toString());
                     
                     return callback(model);
                })
                .catch((ex) => {
                    model.error = {
                        message: typeof ex.toString === "function" ? ex.toString() : null,
                        stack: ex.hasOwnProperty("stack") ? ex.stack : null
                    };
                    return callback(model);
                });    


            }).catch((ex) => {
                model.error = {
                    message: typeof ex.toString === "function" ? ex.toString() : null,
                    stack: ex.hasOwnProperty("stack") ? ex.stack : null
                };
                return callback(model);
            });
        })
        .catch((ex) => {
            model.error = {
                message: typeof ex.toString === "function" ? ex.toString() : null,
                stack: ex.hasOwnProperty("stack") ? ex.stack : null
            };
            return callback(model);
        });
}

let mapValidationResults = (results) => {
    if(results === null)
        return [];
    
    var returns = [];
    for(var res of results){
        let mappedRes = {
            message: res.message,
            level: res.level,
            location: res.location
        };
        returns.push(mappedRes);
    }
    return returns;
}



let mapShapesInReferences = (baseUnit, model) => {
    for (let i = 0; i < baseUnit.references().length; i++) {
        let refUnit = baseUnit.references()[i];
        let declaresInRef = refUnit.declares;
        if(declaresInRef){
            let moreShapes = mapDeclaredShapes(declaresInRef);
            for (let j = 0; j < moreShapes.length; j++) {
                model.shapes.push(moreShapes[j]);
            }
        }
        mapShapesInReferences(refUnit, model); // recursively check for refs
    }
}

let mapDeclaredShapes = (declares) => {
    if(declares === null)
        return [];
    
    var returns = [];
    for(var decl of declares){
        var res = null;
        if('inherits' in decl){ // is a shape
            res = mapShape(decl);
        }
        returns.push(res);
    }
    return returns;
}

let mapModel = (amfModel) => {
    let encodes = amfModel.encodes;
    if(encodes == null)
        return null;
    
    let mappedModel = {
        raw: amfModel.raw,
        name: value(encodes.name),
        description: value(encodes.description),
        host: value(encodes.host),
        schemes: mapValuesArray(encodes.schemes),
        endpoints: mapEndpoints(encodes.endPoints),
        servers: mapServers(encodes.servers),
        accepts: mapValuesArray(encodes.accepts),
        contentType: mapValuesArray(encodes.contentType),
        version: value(encodes.version),
        termsOfService: value(encodes.termsOfService),
        provider: mapOrganization(encodes.provider),
        license: mapLicense(encodes.license),
        documentations: mapDocumentations(encodes.documentations),
        baseUriParameters: mapParameters(encodes.baseUriParameters),
        security: mapSecurities(encodes.security),
        idsMap: idsMap
    }
    return mappedModel;
}

let mapServers = (servers) => {
    if(!servers)
        return [];
    
    var returns = [];
    for(let server of servers){
        let res = value(server.url);
        returns.push(res);
    };
    return returns;
}

let mapOrganization = (organization) => {
    if(!organization)
        return null;

    return {
        url: value(organization.url),
        name: value(organization.name),
        email: value(organization.email)
    }
}

let mapLicense = (license) => {
    if(!license)
        return null;

    return {
        url: value(license.url),
        name: value(license.name)
    }
}

let mapEndpoints = (endpoints) => {
    if(!endpoints)
        return [];
    
    var returns = [];
    for(let endpoint of endpoints) {
        let res = {
            name: value(endpoint.name),
            relativePath: endpoint.relativePath,
            path: value(endpoint.path),
            description: value(endpoint.description),
            operations: mapOperations(endpoint.operations),
            parameters: mapParameters(endpoint.parameters),
            security: mapSecurities(endpoint.security)
        };
        returns.push(res);
    }
    return returns;
}

let mapOperations = (operations) => {
    if(!operations)
        return [];

    var returns = [];
    for(let op of operations){
        let res = {
            method: value(op.method),
            name: value(op.name),
            description: value(op.description),
            deprecated: value(op.deprecated),
            summary: value(op.summary),
            documentation: mapDocumentation(op.documentation),
            schemes: mapValuesArray(op.schemes),
            accepts: mapValuesArray(op.accepts),
            contentType: mapValuesArray(op.contentType),
            request: mapRequest(op.request),
            responses: mapResponses(op.responses),
            security: mapSecurities(op.security)
        };
        returns.push(res);
    }
    return returns;
}

let mapDocumentations = (documentations) => {
    if(!documentations)
        return [];
    
    var returns = [];
    for(let doc of documentations){
        let res = mapDocumentation(doc);
        returns.push(res);
    }
    return returns;
}

let mapDocumentation = (documentation) => {
    if(!documentation)
        return null;

    return {
        url: value(documentation.url),
        description: value(documentation.description),
        title: value(documentation.title)
    };
}

let mapRequest = (request) => {
    if(!request)
        return null;
    
    return {
        queryParameters: mapParameters(request.queryParameters),
        headers: mapParameters(request.headers),
        payloads: mapPayloads(request.payloads),
        queryString: mapShape(request.queryString)
    }
}

let mapResponses = (responses) => {
    if(!responses)
        return [];
    
    var returns = [];
    for(let response of responses){
        let res = {
            name: value(response.name),
            description: value(response.description),
            statusCode: value(response.statusCode),
            headers: mapParameters(response.headers),
            payloads: mapPayloads(response.payloads),
            examples: mapExamples(response.examples)
        }
        returns.push(res);
    }
    return returns;
}

let mapSecurities = (securities) => {
    if(!securities)
        return [];
    
    var returns = [];
    for(let sec of securities){
        let res = mapSecurityRequirement(sec);
        returns.push(res);
    }
    return returns;
}

let mapSecurityRequirement = (securityReq) => {
    if(!securityReq)
        return null;

    return {
        schemes: mapParamSecuritiesSchemes(securityReq.schemes)
    };
}

let mapParamSecuritiesSchemes = (securities) => {
    if(!securities)
        return [];
    
    let returns = [];
    for(let sec of securities){
        let res = mapParamSecurityScheme(sec);
        returns.push(res);
    }
    return returns;
}

let mapParamSecurityScheme = (security) => {
    if(!security)
        return null;
    
    return {
        name: value(security.name),
        scheme: mapSecurityScheme(security.scheme),
        settings: mapSettings(security.settings)
    };
}

let mapSecuritySchemes = (securitySchemes) => {
    if(!securitySchemes)
        return [];
    
    let returns = [];
    for(let sec of securitySchemes){
        var res = mapSecurityScheme(sec);
        returns.push(res);
    }
    return returns;
}

let mapSecurityScheme = (scheme) => {
    if(!scheme)
        return null;
    
    return {
        name: value(scheme.name),
        type: value(scheme.type),
        displayName: value(scheme.displayName),
        description: value(scheme.description),
        headers: mapParameters(scheme.headers),
        queryParameters: mapParameters(scheme.queryParameters),
        responses: mapResponses(scheme.responses),
        settings: mapSettings(scheme.settings, value(scheme.type)),
        queryString: mapShape(scheme.queryString)
    };
}

let mapSettings = (settings, type) => {
    if(!settings)
        return null;

    if(type === 'OAuth 1.0'){
        return {
            requestTokenUri: value(settings.requestTokenUri),
            authorizationUri: value(settings.authorizationUri),
            tokenCredentialsUri: value(settings.tokenCredentialsUri),
            signatures: mapValuesArray(settings.signatures)
        };
    }

    if(type === 'OAuth 2.0'){
        return {
            authorizationGrants: mapValuesArray(settings.authorizationGrants),
            flows: mapFlows(settings.flows)
        };
    }

    if(type === 'ApiKey'){
        return {
            name: value(settings.name),
            in: value(settings.in)
        };
    }
    
    return {
        requestTokenUri: value(settings.requestTokenUri),
        authorizationUri: value(settings.authorizationUri),
        tokenCredentialsUri: value(settings.tokenCredentialsUri),
        signatures: mapValuesArray(settings.signatures),
        accessTokenUri: value(settings.accessTokenUri),
        authorizationGrants: mapValuesArray(settings.authorizationGrants),
        flow: value(settings.flow),
        scopes: mapScopes(settings.scopes),
        name: value(settings.name),
        in: value(settings.in)
    };
}

let mapFlows = (flows) => {
    if(!flows)
        return [];
    
    let returns = [];
    for(let flow of flows){
        let res = {
            authorizationUri: value(flow.authorizationUri),
            accessTokenUri: value(flow.accessTokenUri),
            flow: value(flow.flow),
            scopes: mapScopes(flow.scopes)
        };
        returns.push(res);
    }
    return returns;
}

let mapScopes = (scopes) => {
    if(!scopes)
        return [];

    let returns = [];
    for(let scope of scopes){
        let res = {
            name: value(scope.name),
            description: value(scope.description)
        };
        returns.push(res);
    }
    return returns;
}

let mapParameters = (parameters) => {
    if(!parameters)
        return [];
    
    let returns = [];
    for(let param of parameters){
        let res = {
            name: value(param.name),
            description: value(param.description),
            required: value(param.required),
            binding: value(param.binding),
            schema: mapSchema(param.schema)
        };
        returns.push(res);
    }
    return returns;
}

let mapPayloads = (payloads) => {
    if(!payloads)
        return [];
    
    let returns = [];
    for(let payload of payloads){
        let res = {
            mediaType: value(payload.mediaType),
            schema: mapSchema(payload.schema)
        };
        returns.push(res);
    }
    return returns;
}

let mapSchema = (schema) => {
    if(!schema)
        return null;

    let result = mapShape(schema);
    // result.mediaType = value(schema.mediaType);
    // result.raw = value(schema.raw);
    return result;
}

let idsMap = {};

let mapShape = (shape) => {
    if(!shape)
        return null;

    // let obj;
    // if(typeof(resolvedModel) != "undefined" && typeof(shape.annotations().resolvedLink) != "undefined")
    //     obj = resolvedModel.findById(shape.annotations().resolvedLink);
    
    // if(typeof(shape.annotations().resolvedLink) != "undefined" && shape.annotations().resolvedLink != shape.id)
    // {
    //     var key = (' ' + shape.annotations().resolvedLink).slice(1);
    //     idsMap[key] = (' ' + shape.id).slice(1);
    // }

    return {
        // shape
        id: shape.id,
        resolvedLink: typeof shape.annotations === "function" && typeof shape.annotations().resolvedLink === "string" ? shape.annotations().resolvedLink : null,
        resolvedLinkTarget: typeof shape.annotations === "function" && typeof shape.annotations().resolvedLinkTarget === "string" ? shape.annotations().resolvedLinkTarget : null,
        inheritanceProvenance: typeof shape.annotations === "function" && typeof shape.annotations().inheritanceProvenance === "string" ? shape.annotations().inheritanceProvenance : null,
        name: value(shape.name),
        displayName: value(shape.displayName),
        description: value(shape.description),
        default: mapShape(shape.defaultValue),
        values: mapEnumValues(shape.values),
        inherits: mapShapes(shape.inherits),
        isFile: shape instanceof amf.model.domain.FileShape,

        // any
        documentation: mapDocumentation(shape.documentation),
        xmlSerialization: mapXmlSerialization(shape.xmlSerialization),
        examples: mapExamples(shape.examples),

        // union
        anyOf: mapShapes(shape.anyOf),

        // array
        minItems: value(shape.minItems),
        maxItems: value(shape.maxItems),
        uniqueItems: value(shape.uniqueItems),
        items: mapShape(shape.items),
        linkTarget: mapLinkTarget(shape.linkTarget),
        linkLabel: value(shape.linkLabel),

        // scalar
        dataType: value(shape.dataType),
        pattern: value(shape.pattern),
        minLength: value(shape.minLength),
        maxLength: value(shape.maxLength),
        minimum: value(shape.minimum),
        maximum: value(shape.maximum),
        exclusiveMinimum: value(shape.exclusiveMinimum),
        exclusiveMaximum: value(shape.exclusiveMaximum),
        format: value(shape.format),
        multipleOf: value(shape.multipleOf),
        
        // node
        minProperties: value(shape.minProperties),
        maxProperties: value(shape.maxProperties),
        closed: value(shape.closed),
        discriminator: value(shape.discriminator),
        discriminatorValue: value(shape.discriminatorValue),
        readOnly: value(shape.readOnly),
        properties: mapProperties(shape.properties),
        dependencies: mapDependencies(shape.dependencies),
        
        // file
        fileTypes: mapValuesArray(shape.fileTypes)
    }
}

let value = (val) => {
    if(typeof val != 'undefined') {

        if (val === null)
            return null;

        if (typeof val.isNull != 'undefined'){
            return val.isNull ? null : val.value();
        }
        return null;
    }
    return null;
}

let mapValuesArray = (array) => {
    if(array == null)
        return [];
    
    let returns = [];
    for(let val of array) {
        let res = value(val);
        returns.push(res);
    }
    return returns;
}

let mapEnumValues = (values) => {
    if(values == null)
        return [];
    
    if(typeof(values) == "function")
        return [];

    let returns = [];
    for(let val of values) {
        
        let res = null;

        if(typeof val.value.option == "string")
            res = val.value.option;

        if(typeof val.value.dataType == "string")
            res = val.value.dataType;

        if(res == null)
            throw new Error("enum value of uknown type");
        
        returns.push(res);
    }
    return returns;
}

let mapLinkTarget = (linkTarget) => {
    if (linkTarget == null)
        return null;

    return {
        name: linkTarget.id.substring(linkTarget.id.lastIndexOf('#') + 1)
    };
}

let mapProperties = (properties) => {
    if(!properties)
        return [];

    let returns = [];
    for(let prop of properties) {
        let res = {
            path: value(prop.path),
            range: mapShape(prop.range),
            minCount: value(prop.minCount),
            maxCount: value(prop.maxCount),
            inheritanceProvenance: typeof prop.annotations === "function"
                && typeof prop.annotations().inheritanceProvenance === "string"
                ? prop.annotations().inheritanceProvenance
                : null
        };
        returns.push(res);
    }
    return returns;
}

let mapDependencies = (dependencies) => {
    if(!dependencies)
        return [];

    let returns = [];
    for(let dep of dependencies){
        let res = {
            propertySource: value(dep.propertySource),
            propertyTarget: value(dep.propertyTarget)
        };
        returns.push(res);
    }
    return returns;
}

let mapShapes = (shapes) => {
    if(!shapes)
        return [];

    let returns = [];
    for(let shape of shapes){
        let res = mapShape(shape);
        returns.push(res);
    }
    return returns;
}

let mapAnyShape = (shape) => {
    if(!shape)
        return null;

    return {
        documentation: mapDocumentation(shape.documentation),
        examples: mapExamples(shape.examples),
        xmlSerialization: mapXmlSerialization(shape.xmlSerialization)
    }
}

let mapExamples = (examples) => {
    if(!examples)
        return [];

    let returns = [];
    for(let example of examples){
        let res = {
            name: value(example.name),
            displayName: value(example.displayName),
            description: value(example.description),
            value: value(example.value),
            strict: value(example.strict),
            mediaType: value(example.mediaType)
        };
        returns.push(res);
    }
    return returns;
}

let mapXmlSerialization = (xmlSerialization) => {
    if(!xmlSerialization)
        return null;
    
    return {
        attribute: value(xmlSerialization.attribute),
        wrapped: value(xmlSerialization.wrapped),
        name: value(xmlSerialization.name),
        namespace: value(xmlSerialization.namespace),
        prefix: value(xmlSerialization.prefix)
    }
}

exports.parse = parse;