/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.camel.component.smpp;

import java.nio.charset.Charset;

import org.apache.camel.Message;
import org.jsmpp.bean.Alphabet;
import org.jsmpp.extra.NegativeResponseException;
import org.jsmpp.session.SMPPSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class SmppSmCommand extends AbstractSmppCommand {

    // FIXME: these constants should be defined somewhere in jSMPP:
    public static final int SMPP_NEG_RESPONSE_MSG_TOO_LONG = 1;

    protected Charset ascii = Charset.forName("US-ASCII");
    protected Charset latin1 = Charset.forName("ISO-8859-1");
    protected Charset defaultCharset;

    private final Logger logger = LoggerFactory.getLogger(SmppSmCommand.class);

    public SmppSmCommand(SMPPSession session, SmppConfiguration config) {
        super(session, config);
        defaultCharset = Charset.forName(config.getEncoding());
    }

    protected byte[][] splitBody(Message message) throws SmppException {
        byte[] shortMessage = getShortMessage(message);
        SmppSplitter splitter = createSplitter(message);
        byte[][] segments = splitter.split(shortMessage);
        if (segments.length > 1) {
            // Message body is split into multiple parts,
            // check if this is permitted
            SmppSplittingPolicy policy = getSplittingPolicy(message);
            switch(policy) {
                case ALLOW:
                    return segments;
                case TRUNCATE:
                    return new byte[][] {java.util.Arrays.copyOfRange(shortMessage, 0, segments[0].length)};
                case REJECT:
                    // FIXME - JSMPP needs to have an enum of the negative response
                    // codes instead of just using them like this
                    NegativeResponseException nre = new NegativeResponseException(SMPP_NEG_RESPONSE_MSG_TOO_LONG);
                    throw new SmppException(nre);
                default:
                    throw new SmppException("Unknown splitting policy: " + policy);
            }
        } else {
            return segments;
        }
    }

    private SmppSplittingPolicy getSplittingPolicy(Message message) throws SmppException {
        if (message.getHeaders().containsKey(SmppConstants.SPLITTING_POLICY)) {
            String policyName = message.getHeader(SmppConstants.SPLITTING_POLICY, String.class);
            return SmppSplittingPolicy.fromString(policyName);
        }
        return config.getSplittingPolicy();
    }

    protected SmppSplitter createSplitter(Message message) throws SmppException {

        SmppSplitter splitter;
        // use the splitter if provided via header
        if (message.getHeaders().containsKey(SmppConstants.DATA_SPLITTER)) {
            splitter = message.getHeader(SmppConstants.DATA_SPLITTER, SmppSplitter.class);
            if (null != splitter) {
                return splitter;
            }
            throw new SmppException("Invalid splitter given. Must be instance of SmppSplitter");
        }
        Alphabet alphabet = determineAlphabet(message);
        String body = message.getBody(String.class);

        if (SmppUtils.is8Bit(alphabet)) {
            splitter = new Smpp8BitSplitter(body.length());
        } else if (alphabet == Alphabet.ALPHA_UCS2) {
            splitter = new SmppUcs2Splitter(body.length());
        } else {
            splitter = new SmppDefaultSplitter(body.length());
        }
        return splitter;
    }

    protected final byte[] getShortMessage(Message message) {
        if (has8bitDataCoding(message)) {
            return message.getBody(byte[].class);
        } else {
            byte providedAlphabet = getProvidedAlphabet(message);
            Alphabet determinedAlphabet = determineAlphabet(message);
            Charset charset = determineCharset(message, providedAlphabet, determinedAlphabet.value());
            String body = message.getBody(String.class);
            return body.getBytes(charset);
        }
    }

    private static boolean has8bitDataCoding(Message message) {
        Byte dcs = message.getHeader(SmppConstants.DATA_CODING, Byte.class);
        if (dcs != null) {
            return SmppUtils.is8Bit(Alphabet.parseDataCoding(dcs.byteValue()));
        } else {
            Byte alphabet = message.getHeader(SmppConstants.ALPHABET, Byte.class);
            return alphabet != null && SmppUtils.is8Bit(Alphabet.valueOf(alphabet));
        }
    }

    private byte getProvidedAlphabet(Message message) {
        byte alphabet = config.getAlphabet();
        if (message.getHeaders().containsKey(SmppConstants.ALPHABET)) {
            alphabet = message.getHeader(SmppConstants.ALPHABET, Byte.class);
        }

        return alphabet;
    }

    private Charset getCharsetForMessage(Message message) {
        if (message.getHeaders().containsKey(SmppConstants.ENCODING)) {
            String encoding = message.getHeader(SmppConstants.ENCODING, String.class);
            if (Charset.isSupported(encoding)) {
                return Charset.forName(encoding);
            } else {
                logger.warn("Unsupported encoding \"{}\" requested in header.", encoding);
            }
        }
        return null;
    }

    private Charset determineCharset(Message message, byte providedAlphabet, byte determinedAlphabet) {
        Charset result = getCharsetForMessage(message);
        if (result != null) {
            return result;
        }

        if (providedAlphabet == Alphabet.ALPHA_UCS2.value()
                || (providedAlphabet == SmppConstants.UNKNOWN_ALPHABET && determinedAlphabet == Alphabet.ALPHA_UCS2.value())) {
            // change charset to use multilang messages
            return Charset.forName(SmppConstants.UCS2_ENCODING);
        }

        return defaultCharset;
    }

    private Alphabet determineAlphabet(Message message) {
        String body = message.getBody(String.class);
        byte alphabet = getProvidedAlphabet(message);
        Charset charset = getCharsetForMessage(message);
        if (charset == null) {
            charset = defaultCharset;
        }

        Alphabet alphabetObj;
        if (alphabet == SmppConstants.UNKNOWN_ALPHABET) {
            alphabetObj = Alphabet.ALPHA_UCS2;
            if (isLatin1Compatible(charset)) {
                byte[] messageBytes = body.getBytes(charset);
                if (SmppUtils.isGsm0338Encodeable(messageBytes)) {
                    alphabetObj = Alphabet.ALPHA_DEFAULT;
                }
            }
        } else {
            alphabetObj = Alphabet.valueOf(alphabet);
        }

        return alphabetObj;
    }

    private boolean isLatin1Compatible(Charset c) {
        if (c.equals(ascii) || c.equals(latin1)) {
            return true;
        }
        return false;
    }
}
