/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package org.etsi.mts.tdl.yang2json;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import org.opendaylight.yangtools.odlext.parser.MountStatementSupport;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.stream.InputStreamNormalizer;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizationException;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
import org.opendaylight.yangtools.yang.data.spi.node.LazyValues;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.spi.source.FileYangTextSource;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
import org.opendaylight.yangtools.yang.parser.rfc7950.reactor.RFC7950Reactors;
import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangStatementStreamSource;
import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
import org.opendaylight.yangtools.yang.parser.stmt.reactor.EffectiveSchemaContext; 
import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor.BuildAction;

// import org.opendaylight.;

// public class App {
//     public String getGreeting() {
//         return "Hello World!";
//     }

//     public static void main(String[] args) {
//         System.out.println(new App().getGreeting());

//     }


public class App {     
    QName system = QName.create("acme-system", "2007-06-09", "system");
    QName login = QName.create(system, "login");
    QName user = QName.create(system, "user"); 
    private LinkedHashMap<String, QName> names = new LinkedHashMap<>();
    private SchemaContext schemaContext;
    private InputStreamNormalizer PARSER;


    public App(String limit, String targetFilename) {
        try {
			init(limit, targetFilename);
		} catch (IOException | ReactorException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }

    public static void main( String[] args ) throws IOException, ReactorException, NormalizationException
    {
        String limit = new File(".").getAbsolutePath();
        String targetFilename = "samples/simple/acme.yang";
        // targetFilename = "yang.parser.test/app/src/main/resources/foo.yang";
        App app = new App(limit, targetFilename);
        app.process();
    }

    private void process()
            throws IOException, ReactorException, NormalizationException {

        NormalizedNode data = parseTopLevelComplete(PARSER);
        NormalizedNode childSingle = parseChildDataSingle(PARSER);
        NormalizedNode childMultiple = parseChildDataMultiple(PARSER);
        writeNodeData(PARSER, data);
        //TODO: partial writing does not work yet
        // writeNestedNodeData(PARSER, data);
        // writeNodeData(PARSER, childSingle);
        // writeNodeData(PARSER, childMultiple);
        // writeNestedNodeData(PARSER, childSingle);
        // writeNestedNodeData(PARSER, childMultiple);
    }


    private void init(String limit, String targetFilename) throws IOException, ReactorException {
        schemaContext = loadSchema(limit, targetFilename);
        schemaContext.getChildNodes().forEach(e->System.out.println(e.getQName().getNamespace()));
        expand("", schemaContext);
        //parse json input
        System.out.println("----------------------------------------------------");
        names.forEach((k,v)-> System.out.println(k+":"+v.getNamespace()));
        System.out.println("----------------------------------------------------");

        PARSER = JSONCodecFactorySupplier.RFC7951.getShared((EffectiveSchemaContext) schemaContext);
    }

    private static CrossSourceStatementReactor createReactor() {
		CrossSourceStatementReactor reactor = RFC7950Reactors.vanillaReactorBuilder()
            .addStatementSupport(ModelProcessingPhase.FULL_DECLARATION,
            new MountStatementSupport(YangParserConfiguration.DEFAULT))
        .build();
		return reactor;
	}


    public void expand(String prefix, DataSchemaNode node) {
        System.out.println(prefix + node.getQName().getNamespace() 
            + " : "+node.getQName().getLocalName() 
            // + " : " + node.getQName().getModule()
            );
        names.put(node.getQName().getLocalName(), node.getQName());
        if (node instanceof DataNodeContainer) {
            for (DataSchemaNode c : ((DataNodeContainer) node).getChildNodes()) {
                expand(prefix+"  ", c);
            }
        }
    }

    private NormalizedNode parseTopLevelComplete(InputStreamNormalizer PARSER)
            throws NormalizationException, IOException {
    	String json = Files.readString(Path.of("samples", "json", "acme.system.json"));
        NormalizedNode data = PARSER.parseChildData(Inference.of((EffectiveSchemaContext) schemaContext), stream(json)).result().data();

        System.out.println(data.prettyTree());
        return data;
    }

    private NormalizedNode parseChildDataMultiple(InputStreamNormalizer PARSER)
            throws NormalizationException, IOException {
        SchemaInferenceStack stack = SchemaInferenceStack.of((EffectiveModelContext) schemaContext);
        stack.enterSchemaTree(system);
        stack.enterSchemaTree(login);
        stack.enterSchemaTree(user);
        String json = Files.readString(Path.of("samples", "json", "acme.system.login.user.json"));
        NormalizedNode data = PARSER.parseData(
            stack.toInference(), 
            stream(json)).data();
        System.out.println(data.prettyTree());
        return data;
    }

    private NormalizedNode parseChildDataSingle(InputStreamNormalizer PARSER)
            throws NormalizationException, IOException {
        SchemaInferenceStack stack = SchemaInferenceStack.of((EffectiveModelContext) schemaContext);
        stack.enterSchemaTree(system);
        stack.enterSchemaTree(login);
        String json = Files.readString(Path.of("samples", "json", "acme.system.login.json"));
        NormalizedNode childData = PARSER.parseChildData(
            stack.toInference(), 
            stream(json)).result().data();
        System.out.println(childData.prettyTree());
        return childData;
    }

    private void writeNestedNodeData(InputStreamNormalizer PARSER, NormalizedNode data) throws IOException {
        // nested writer
        final var nestedWriter = new StringWriter();
        // InputStreamNormalizer nPARSER = JSONCodecFactorySupplier.RFC7951.getShared((EffectiveSchemaContext) stack.currentStatement());
        final var jsonNestedStream = JSONNormalizedNodeStreamWriter.createNestedWriter(
            (JSONCodecFactory) PARSER, JsonWriterFactory.createJsonWriter(nestedWriter, 2));
            final var nestedNodeWriter = NormalizedNodeWriter.forStreamWriter(((NormalizedNodeStreamWriter) jsonNestedStream)
        );

        nestedNodeWriter.write(data).close();
        System.out.println(nestedWriter.toString());

        var dcc  = (DataContainerChild)data;
        var b = dcc.body();
        b = ((LazyValues) b).iterator().next().body();
        var c = ((LazyValues) b).iterator().next();
        //TODO: does not work yet
        nestedNodeWriter.write(c).close();
        // Just to put it somewhere
        System.out.println(nestedWriter.toString());
    }

    private void writeNodeData(InputStreamNormalizer PARSER, NormalizedNode data) throws IOException {
        //write json output
        //TODO: need to figure out partial parsing and output
        // String holder
        final var writer = new StringWriter();
        // StreamWriter which outputs JSON strings
        // StreamWriter which outputs JSON strings
        final var jsonStream = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
            (JSONCodecFactory) PARSER, JsonWriterFactory.createJsonWriter(writer, 2));
            
        // NormalizedNode -> StreamWriter
        final var nodeWriter = NormalizedNodeWriter.forStreamWriter((NormalizedNodeStreamWriter) jsonStream);
            
        // Write multiple NormalizedNodes fluently, flush()/close() as needed
        nodeWriter.write(data).close();
        System.out.println(writer.toString());
    }

    private SchemaContext loadSchema(String limit, String targetFilename)
            throws IOException, ReactorException {
        CrossSourceStatementReactor reactor = createReactor();
		BuildAction build = reactor.newBuild();

		//TODO: extract
		//DONE: resolve files -> load everything from the root folder (naive approach)
		//TODO: handle folders as well?
		//TODO: handle duplicates modules?
		System.out.println("Load YANG libraries from parent:");
		Path target = Path.of(targetFilename);
        System.out.println(target.toAbsolutePath().toString());
		Path parent = target;
		while (parent.getParent() != null && !parent.toAbsolutePath().toString().equals(limit)) {
			parent = parent.getParent();
		}
		if (parent != target) {
			Files.walk(parent).filter(f->
					f.toFile().isFile() &&
					f.toString().endsWith(".yang") &&
					!f.toString().equals(targetFilename)
				).forEach(f -> {
					System.out.println(f);
					build.addLibSource(moduleFromPath(f.toString()));
				}
			);
        }

        System.out.println("Processing: "+target);
		if (target.toFile().isDirectory()) {
			Files.walk(target).filter(f->
					f.toFile().isFile() &&
					f.toString().endsWith(".yang") &&
					!f.toString().equals(targetFilename)
					).forEach(f -> {
						System.out.println(f);
						build.addSource(moduleFromPath(f.toString()));
					}
				);
			//non-recursive version
//			Files.list(target)
//				.filter(p->p.toString().endsWith("yang"))
//				.forEach(p -> 
//				build.addSource(moduleFromPath(p.toString()))
//			);
		} else {
			build.addSource(moduleFromPath(targetFilename));
		}
        SchemaContext schemaContext = build.buildEffective();
        return schemaContext;
    }
    private static InputStream stream(final String str) {
        return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
    }
    private static YangStatementStreamSource moduleFromPath(String path) {
    	return moduleFromPath(Path.of(path));
    }
    private static YangStatementStreamSource moduleFromPath(Path path) {
        try {
            // return new YangStatementSourceImpl("/example.yang", false);
            return YangStatementStreamSource.create(new FileYangTextSource(path));
            // return YangStatementStreamSource.create(YangTextSource.forResource(path));
        } catch (final YangSyntaxErrorException | IOException e) {
            throw new IllegalStateException("Failed to find resource " + path.toString(), e);
        }
    }

}

// }
