package org.zstack.core.errorcode;

import org.zstack.core.errorcode.schema.Error;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.ErrorCodeList;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.path.PathUtil;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 */
public class ErrorFacadeImpl implements ErrorFacade {
    private static final CLogger logger = Utils.getLogger(ErrorFacadeImpl.class);
    private Map<String, ErrorCodeInfo> codes = new HashMap<>();
    private boolean dumpOnError = Boolean.valueOf(System.getProperty("ErrorFacade.dumpOnError"));

    @Override
    public ErrorCode instantiateErrorCode(Enum code, ErrorCode cause) {
        return instantiateErrorCode(code.toString(), cause);
    }

    @Override
    public ErrorCode instantiateErrorCode(String code, ErrorCode cause) {
        return instantiateErrorCode(code, cause.getDetails(), cause);
    }

    @Override
    public ErrorCode instantiateErrorCode(Enum code, String details, ErrorCode cause) {
        return instantiateErrorCode(code.toString(), details, cause);
    }

    @Override
    public ErrorCode instantiateErrorCode(String code, String details, ErrorCode cause) {
        ErrorCode err = instantiateErrorCode(code, details);
        err.setCause(cause);
        return err;
    }

    @Override
    public ErrorCode instantiateErrorCode(Enum code, String details) {
        return instantiateErrorCode(code.toString(), details);
    }

    private ErrorCode doInstantiateErrorCode(String code, String details, List<ErrorCode> causes) {
        ErrorCodeInfo info = codes.get(code);
        if (info == null) {
            throw new CloudRuntimeException(String.format("cannot find error code[%s]", code));
        }

        if (details != null && details.length() > 4096) {
            details = details.substring(0, Math.min(details.length(), 4096));
        }
        ErrorCodeList err = (ErrorCodeList) info.code.copy();
        err.setDetails(details);
        err.setCauses(causes);

        if (dumpOnError) {
            DebugUtils.dumpStackTrace(String.format("An error code%s is instantiated," +
                    " for tracing the place error happened, dump stack as below", err));
        }

        return err;
    }

    @Override
    public ErrorCode instantiateErrorCode(String code, String details) {
        return doInstantiateErrorCode(code, details, null);
    }

    @Override
    public ErrorCode stringToInternalError(String details) {
        return instantiateErrorCode(SysErrors.INTERNAL.toString(), details);
    }

    @Override
    public ErrorCode throwableToInternalError(Throwable t) {
        return instantiateErrorCode(SysErrors.INTERNAL.toString(), t.getMessage());
    }

    @Override
    public ErrorCode stringToTimeoutError(String details) {
        return instantiateErrorCode(SysErrors.TIMEOUT.toString(), details);
    }

    @Override
    public ErrorCode throwableToTimeoutError(Throwable t) {
        return instantiateErrorCode(SysErrors.TIMEOUT.toString(), t.getMessage());
    }

    @Override
    public ErrorCode stringToOperationError(String details) {
        return instantiateErrorCode(SysErrors.OPERATION_ERROR, details);
    }

    @Override
    public ErrorCode stringToOperationError(String details, ErrorCode cause) {
        return instantiateErrorCode(SysErrors.OPERATION_ERROR, details, cause);
    }

    @Override
    public ErrorCodeList instantiateErrorCode(Enum code, List<ErrorCode> causes) {
        return instantiateErrorCode(code.toString(), causes);
    }

    @Override
    public ErrorCodeList instantiateErrorCode(String code, List<ErrorCode> causes) {
        return instantiateErrorCode(code, null, causes);
    }

    @Override
    public ErrorCodeList instantiateErrorCode(Enum code, String details, List<ErrorCode> causes) {
        return instantiateErrorCode(code.toString(), details, causes);
    }

    @Override
    public ErrorCodeList instantiateErrorCode(String code, String details, List<ErrorCode> causes) {
        return (ErrorCodeList) doInstantiateErrorCode(code, details, causes);
    }

    @Override
    public ErrorCodeList stringToOperationError(String details, List<ErrorCode> causes) {
        return instantiateErrorCode(SysErrors.OPERATION_ERROR, details, causes);
    }

    @Override
    public ErrorCode throwableToOperationError(Throwable t) {
        return instantiateErrorCode(SysErrors.OPERATION_ERROR, t.getMessage());
    }

    @Override
    public ErrorCode stringToInvalidArgumentError(String details) {
        return instantiateErrorCode(SysErrors.INVALID_ARGUMENT_ERROR, details);
    }

    @Override
    public ErrorCode throwableToInvalidArgumentError(Throwable t) {
        return instantiateErrorCode(SysErrors.INVALID_ARGUMENT_ERROR, t.getMessage());
    }

    private class ErrorCodeInfo {
        ErrorCode code;
        String path;
    }

    private void createErrorCode(org.zstack.core.errorcode.schema.Error error, String path) {
        for (Error.Code code : error.getCode()) {
            String codeId = String.format("%s.%s", error.getPrefix(), code.getId());
            ErrorCodeInfo info = codes.get(codeId);
            if (info != null) {
                throw new CloudRuntimeException(String.format("duplicate definition of ErrorCode[%s]," +
                        " file[%s] and file[%s] both define it", codeId, info.path, path));
            }

            ErrorCodeList errorCode = new ErrorCodeList();
            errorCode.setCode(codeId);
            errorCode.setDescription(code.getDescription());
            errorCode.setElaboration(code.getElaboration());
            info = new ErrorCodeInfo();
            info.code = errorCode;
            info.path = path;
            codes.put(codeId, info);
        }
    }

    void init() {
        try {
            JAXBContext context = JAXBContext.newInstance("org.zstack.core.errorcode.schema");
            List<String> paths = PathUtil.scanFolderOnClassPath("errorCodes");
            for (String p : paths) {
                if (!p.endsWith(".xml")) {
                    logger.warn(String.format("ignore %s which is not ending with .xml", p));
                    continue;
                }

                File cfg = new File(p);
                Unmarshaller unmarshaller = context.createUnmarshaller();
                org.zstack.core.errorcode.schema.Error error =
                        (org.zstack.core.errorcode.schema.Error) unmarshaller.unmarshal(cfg);
                createErrorCode(error, p);
            }
        } catch (Exception e) {
            throw new CloudRuntimeException(e);
        }
    }
}

@@ -2155,41 +2155,21 @@
 (0, 
-Math.min(details.length(), 4096))
+4093) + %22...%22
 ;%0A  

enhance long error details Resolves ZSTAC-15333