package org.wso2.carbon.device.mgt.ios.util;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.ios.api.utils.util.OperationMapping;
import org.wso2.carbon.device.mgt.ios.apns.exception.APNSException;
import org.wso2.carbon.device.mgt.ios.core.exception.IOSEnrollmentException;
import org.wso2.carbon.device.mgt.ios.core.service.IOSEnrollmentService;
import org.wso2.carbon.device.mgt.ios.core.util.AppConfigurations;
import org.wso2.carbon.device.mgt.ios.payload.beans.PayloadOperation;
import org.wso2.carbon.device.mgt.ios.payload.dto.PayloadOperationWrapper;
import org.wso2.carbon.device.mgt.ios.payload.exception.PListException;
import org.wso2.carbon.device.mgt.ios.payload.exception.ProfileConfigurationException;
import org.wso2.carbon.device.mgt.ios.payload.operation.InstallProfileOperation;
import org.wso2.carbon.device.mgt.ios.payload.operation.PolicyOperations;
import org.wso2.carbon.policy.mgt.core.util.PolicyManagementConstants;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

public class OperationUtils {

    private static final String POLICY_ID_PREFIX = "policy_";
    private static final int BUFFER_SIZE = 1024;
    private static final Log log = LogFactory.getLog(OperationUtils.class);

    public Operation getPendingOperation(String deviceId)
            throws DeviceManagementException, OperationManagementException {

        DeviceIdentifier deviceIdentifier = new DeviceIdentifier();
        deviceIdentifier.setId(deviceId);
        deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS);

        Operation operation = IOSServiceUtils.getDeviceManagementService().
                getNextPendingOperation(deviceIdentifier);

        return operation;
    }

    public void updateOperation(Operation operation, DeviceIdentifier deviceId)
            throws DeviceManagementException, OperationManagementException {

        IOSServiceUtils.getDeviceManagementService().updateOperation(deviceId, operation);
    }

    public void updateOperationWithErrorStatus(String commandUUID, String operationError,
                                               DeviceIdentifier deviceIdentifier) {

        if(commandUUID == null || commandUUID.isEmpty() || !StringUtils.isNumeric(commandUUID)) {
            return;
        }

        try {
            Operation operation = new Operation();
            operation.setId(Integer.valueOf(commandUUID));
            operation.setStatus(Operation.Status.ERROR);
            operation.setOperationResponse(operationError);

            updateOperation(operation, deviceIdentifier);
        } catch (DeviceManagementException e) {
            log.error("DeviceManagementException occurred when saving operation error status", e);
        } catch (OperationManagementException e) {
            log.error("OperationManagementException occurred when saving operation error status", e);
        }

    }

    public void updateApplicationList(List<Application> applications, DeviceIdentifier deviceId)
            throws ApplicationManagementException {

        IOSServiceUtils.getApplicationManagementService().
                updateApplicationListInstalledInDevice(deviceId, applications);
    }

    public String generateOperationPayload(Device device, Operation operation, X509Certificate certificate)
            throws ProfileConfigurationException, PListException, APNSException, IOSEnrollmentException {

        OperationMapping operationMapping = OperationMapping.getEnumByString(operation.getCode());

        if (operationMapping == null) {
            throw new ProfileConfigurationException(
                    "Cannot map operation code: '" + operation.getCode() + "' into an operation.");
        }

        OperationMapper operationMapper = new OperationMapper();
        String profileOperationPayload;

        String operationID = Integer.valueOf(operation.getId()).toString();

        if (operationMapping == OperationMapping.MONITOR) {
            operationMapping = OperationMapping.PROFILE_LIST;
        }

        String commandOperationPayload = operationMapper.mapToCommandOperations(device, operationMapping, operation);

        if(commandOperationPayload != null) {
            return commandOperationPayload;
        }

        InstallProfileOperation installProfileOperation = new InstallProfileOperation();
        IOSEnrollmentService enrollmentService = IOSServiceUtils.getEnrollmentService();

        if(PolicyManagementConstants.POLICY_BUNDLE.equals(operation.getCode())) {

            List<Operation> operations =  (ArrayList<Operation>)operation.getPayLoad();

            PayloadOperationWrapper policyPayloadOperationWrapper = new PayloadOperationWrapper();
            policyPayloadOperationWrapper.setPayloadProperties(
                    new PolicyOperations().generatePayloadProperties(PolicyManagementConstants.POLICY_BUNDLE));
            List<Object> payloads = new ArrayList<>();

            int policyCounter = 0;

            for (Operation policyOperation : operations) {

                OperationMapping policyOperationMapping = OperationMapping.getEnumByString(policyOperation.getCode());

                if (operationMapping == null) {
                    continue;
                }

                String policyOperationID = POLICY_ID_PREFIX + policyCounter++;
                PayloadOperationWrapper payloadOperationWrapper = operationMapper.mapToProfileOperation(device,
                        policyOperationMapping, (String)policyOperation.getPayLoad(), policyOperationID);

                payloads.addAll(payloadOperationWrapper.getPayload());
            }

            policyPayloadOperationWrapper.setPayload(payloads);

            profileOperationPayload = PayloadOperation.generateOperationPayload(
                    policyPayloadOperationWrapper, operationID);

        } else {
            PayloadOperationWrapper payloadOperationWrapper = operationMapper.mapToProfileOperation(device,
                    operationMapping, (String) operation.getPayLoad(), operationID);

            profileOperationPayload = PayloadOperation.generateOperationPayload(payloadOperationWrapper, operationID);

        }

        try {
            byte[] signedData = enrollmentService.getSignedData(profileOperationPayload.
                    getBytes(AppConfigurations.UTF_8));

            return installProfileOperation.getInstallProfilePayload(operationID, signedData);
        } catch (UnsupportedEncodingException e) {
            throw new ProfileConfigurationException("Unsupported encoding. UTF-8 character set is not supported.");
        }
    }

    public InputStream[] cloneInputStream(InputStream inputStream) throws IOSEnrollmentException {

        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            byteArrayOutputStream = new ByteArrayOutputStream();

            byte[] buffer = new byte[BUFFER_SIZE];
            int len;
            while ((len = inputStream.read(buffer)) > -1 ) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byteArrayOutputStream.flush();

            InputStream stream1 = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            InputStream stream2 = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            InputStream stream3 = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

            return new InputStream[]{stream1, stream2, stream3};
        } catch (IOException e) {
            throw new IOSEnrollmentException("Error occurred while cloning input stream ", e);
        } finally {
            if (byteArrayOutputStream != null) {
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {

                }
            }
        }
    }

    public boolean isOneWayOperation(Operation operation) {

        //Further modify this method if there exist any one way operations which does not return any operation response.
        return (OperationMapping.NOTIFICATION.getCode().equals(operation.getCode()));
    }

}
