package api

import (
	"strings"

	chainjson "github.com/vapor/encoding/json"
	"github.com/vapor/equity/compiler"
	"github.com/vapor/errors"
	"github.com/vapor/protocol/vm"
)

// pre-define contract error types
var (
	ErrCompileContract = errors.New("compile contract failed")
	ErrInstContract    = errors.New("instantiate contract failed")
)

type (
	compileReq struct {
		Contract string                 `json:"contract"`
		Args     []compiler.ContractArg `json:"args"`
	}

	compileResp struct {
		Name    string             `json:"name"`
		Source  string             `json:"source"`
		Program chainjson.HexBytes `json:"program"`
		Params  []*compiler.Param  `json:"params"`
		Value   string             `json:"value"`
		Clauses []*compiler.Clause `json:"clause_info"`
		Opcodes string             `json:"opcodes"`
		Error   string             `json:"error"`
	}
)

func compileEquity(req compileReq) (*compileResp, error) {
	compiled, err := compiler.Compile(strings.NewReader(req.Contract))
	if err != nil {
		return nil, errors.WithDetail(ErrCompileContract, err.Error())
	}

	// if source contract maybe contain import statement, multiple contract objects will be generated
	// after the compilation, and the last object is what we need.
	contract := compiled[len(compiled)-1]
	resp := &compileResp{
		Name:    contract.Name,
		Source:  req.Contract,
		Program: contract.Body,
		Value:   contract.Value.Amount + " of " + contract.Value.Asset,
		Clauses: contract.Clauses,
		Opcodes: contract.Opcodes,
	}

	if req.Args != nil {
		resp.Program, err = compiler.Instantiate(contract.Body, contract.Params, contract.Recursive, req.Args)
		if err != nil {
			return nil, errors.WithDetail(ErrInstContract, err.Error())
		}

		resp.Opcodes, err = vm.Disassemble(resp.Program)
		if err != nil {
			return nil, err
		}
	}

	for _, param := range contract.Params {
		if param.InferredType != "" {
			param.Type = param.InferredType
			param.InferredType = ""
		}
		resp.Params = append(resp.Params, param)
	}

	return resp, nil
}

func (a *API) compileEquity(req compileReq) Response {
	resp, err := compileEquity(req)
	if err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(&resp)
}
