/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 */
package compilers.go;

import java.io.FileWriter;
import java.io.PrintWriter;

import org.thingml.compilers.Context;
import org.thingml.compilers.ThingMLCompiler;
import org.thingml.compilers.builder.Element;
import org.thingml.compilers.builder.Section;
import org.thingml.compilers.configuration.CfgBuildCompiler;
import org.thingml.compilers.utils.OpaqueThingMLCompiler;
import org.thingml.utilities.logging.Logger;
import org.thingml.xtext.constraints.ThingMLHelpers;
import org.thingml.xtext.helpers.AnnotatedElementHelper;
import org.thingml.xtext.helpers.ConfigurationHelper;
import org.thingml.xtext.thingML.Configuration;
import org.thingml.xtext.thingML.Enumeration;
import org.thingml.xtext.thingML.EnumerationLiteral;
import org.thingml.xtext.thingML.Thing;
import org.thingml.xtext.thingML.ThingMLModel;

public class GoCompiler extends OpaqueThingMLCompiler {
	
	public GoCompiler() {
		super(new GoThingActionCompiler(),
			  new GoThingApiCompiler(),
			  new GoCfgMainGenerator(),
			  new CfgBuildCompiler(),
			  new GoThingImplCompiler());
	}

	@Override
	public ThingMLCompiler clone() {
		return new GoCompiler();
	}

	@Override
	public String getID() {
		return "go";
	}

	@Override
	public String getName() {
		return "Go";
	}

	@Override
	public String getDescription() {
		return "Generates Go code";
	}
	
	private void generateEnumerations(ThingMLModel model, GoSourceBuilder builder, GoContext gctx) {
		for (Enumeration enumeration : ThingMLHelpers.allEnumerations(model)) {
			Section enumS = builder.appendSection("enumeration").lines();
			enumS.appendSection("type")
				 .append("type ")
				 .append(gctx.getTypeName(enumeration))
				 .append(" int"); // TODO: Use @go_type if present on enum
			Section constS = enumS.appendSection("consts").lines();
			constS.appendSection("before")
				  .append("const (");
			Section constBody = constS.appendSection("body").lines().indent();
			for (EnumerationLiteral literal : enumeration.getLiterals()) {
				Element value = new Element("iota");
				constBody.appendSection("literal")
					     .append(enumeration.getName())
						 .append(literal.getName())
						 .append(" ")
						 .append(gctx.getTypeName(enumeration))
						 .append(" = ")
						 .append(value);
				if (AnnotatedElementHelper.hasAnnotation(literal, "enum_val"))
					value.set(AnnotatedElementHelper.firstAnnotation(literal, "enum_val"));
				
			}
			constS.appendSection("after")
				  .append(")");
		}
	}

	@Override
	public boolean do_call_compiler(Configuration cfg, Logger log, String... options) {		
		GoContext ctx = new GoContext(this, log);
		
		// Check if we should do auto-casting
		if (AnnotatedElementHelper.isDefined(cfg, "go_autocast", "true")) {
			ctx.shouldAutocast = true;
		}
		
		// Generate types
		GoSourceBuilder typesBuilder = ctx.getSourceBuilder(ctx.getTypesPath());
		typesBuilder.append("package main").append("");
		generateEnumerations(ThingMLHelpers.findContainingModel(cfg), typesBuilder, ctx);
		
		// Generate thing code
		for (Thing t : ConfigurationHelper.allUsedThings(cfg)) {
			ctx.setCurrentThingContext(t);
			getThingApiCompiler().generatePublicAPI(t, ctx);
			getThingImplCompiler().generateImplementation(t, ctx);
			ctx.unsetCurrentThingContext(t);
		}
		
		// Generate main function
		getMainCompiler().generateMainAndInit(cfg, ThingMLHelpers.findContainingModel(cfg), ctx);
		getCfgBuildCompiler().generateDockerFile(cfg, ctx);
		ctx.writeGeneratedCodeToFiles();

		return true;
	}
	
	@Override
    public String getDockerBaseImage(Configuration cfg, Context ctx) {
        return "golang:alpine";
    }
    
    @Override
    public String getDockerCMD(Configuration cfg, Context ctx) {
        return "/" + cfg.getName();
    }
    
    @Override
    public String getDockerCfgRunPath(Configuration cfg, Context ctx) {
        return "RUN mkdir -p /go/src/" + cfg.getName() + "\n" +
        		"WORKDIR /go/src/" + cfg.getName() + "\n" +
        		"RUN apk add --no-cache build-base git\n" +
        		"RUN go get github.com/jakhog/gosm\n" +
        		"COPY . .\n" +
        		"RUN go build -ldflags \"-linkmode external -extldflags -static\" -o " + cfg.getName() + " -a *.go\n" +
        		"FROM scratch\n" +
        		"COPY --from=0 /go/src/" + cfg.getName() + "/" + cfg.getName() + " /" + cfg.getName() + "\n";
    }
}

@@ -2240,16 +2240,58 @@
 ines();%0A
+%09%09%09Element enumType = new Element(%22int%22);%0A
 %09%09%09enumS
@@ -2398,55 +2398,141 @@
 d(%22 
-int%22); // TODO: Use @go_type if present on enum
+%22)%0A%09%09%09%09 .append(enumType);%0A%09%09%09if (enumeration.getTypeRef() != null)%0A%09%09%09%09enumType.set(gctx.getTypeRef(enumeration.getTypeRef()));%0A%09%09%09%09
 %0A%09%09%09
@@ -3049,16 +3049,325 @@
 value);%0A
+%09%09%09%09if (literal.getInit() != null) %7B%0A%09%09%09%09%09StringBuilder initBuilder = new StringBuilder();%0A%09%09%09%09%09gctx.getCompiler().getThingActionCompiler().generate(literal.getInit(), initBuilder, gctx);%0A%09%09%09%09%09value.set(initBuilder.toString());%0A%09%09%09%09%7D%0A%09%09%09%09%0A%09%09%09%09// FIXME: Remove this. Currently kept for backwards compatibility%0A
 %09%09%09%09if (

Add typed enumerations to Go compiler