package com.wavesplatform.wavesj;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wavesplatform.wavesj.json.WavesJsonMapper;
import com.wavesplatform.wavesj.matcher.CancelOrder;
import com.wavesplatform.wavesj.matcher.DeleteOrder;
import com.wavesplatform.wavesj.matcher.Order;
import com.wavesplatform.wavesj.transactions.LeaseTransaction;
import com.wavesplatform.wavesj.transactions.TransferTransactionV2;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.*;

public class Node {
    private static final String DEFAULT_NODE = "https://testnode4.wavesnodes.com";

    private static final TypeReference<OrderBook> ORDER_BOOK = new TypeReference<OrderBook>() {
    };
    private static final TypeReference<List<Order>> ORDER_LIST = new TypeReference<List<Order>>() {
    };
    private static final TypeReference<List<BlockHeader>> BLOCK_HEADER_LIST = new TypeReference<List<BlockHeader>>() {
    };
    private static final TypeReference<Map<String, Long>> ASSET_DISTRIBUTION = new TypeReference<Map<String, Long>>() {
    };
    private static final TypeReference<AssetDistribution> ASSET_DISTRIBUTION_BY_HEIGHT = new TypeReference<AssetDistribution>() {
    };
    private static final TypeReference<List<AssetBalance>> ASSET_BALANCE_LIST = new TypeReference<List<AssetBalance>>() {
    };
    private static final TypeReference<OrderStatusInfo> ORDER_STATUS = new TypeReference<OrderStatusInfo>() {
    };
    private static final TypeReference<Map<String, Long>> RESERVED = new TypeReference<Map<String, Long>>() {
    };
    private static final TypeReference<Map<String, Object>> TX_INFO = new TypeReference<Map<String, Object>>() {
    };

    private final URI uri;
    private final WavesJsonMapper wavesJsonMapper;
    private final HttpClient client;
    private final byte chainId;

    public Node() {
        try {
            this.uri = new URI(DEFAULT_NODE);
            this.wavesJsonMapper = new WavesJsonMapper((byte) 'T');
            this.client = createDefaultClient();
            this.chainId = (byte) 'T';
        } catch (URISyntaxException e) {
            // should not happen
            throw new RuntimeException(e);
        }
    }

    public Node(String uri, char chainId) throws URISyntaxException {
        this.uri = new URI(uri);
        this.wavesJsonMapper = new WavesJsonMapper((byte) chainId);
        this.client = createDefaultClient();
        this.chainId = (byte) chainId;
    }

    public Node(String uri, byte chainId) throws URISyntaxException {
        this.uri = new URI(uri);
        this.wavesJsonMapper = new WavesJsonMapper(chainId);
        this.client = createDefaultClient();
        this.chainId = chainId;
    }

    public Node(String uri, char chainId, HttpClient httpClient) throws URISyntaxException {
        this.uri = new URI(uri);
        this.wavesJsonMapper = new WavesJsonMapper((byte) chainId);
        this.client = httpClient;
        this.chainId = (byte) chainId;
    }

    public Node(String uri, byte chainId, HttpClient httpClient) throws URISyntaxException {
        this.uri = new URI(uri);
        this.wavesJsonMapper = new WavesJsonMapper(chainId);
        this.client = httpClient;
        this.chainId =  chainId;
    }

    private HttpClient createDefaultClient() {
        return HttpClients.custom().setDefaultRequestConfig(
                RequestConfig.custom()
                        .setSocketTimeout(5000)
                        .setConnectTimeout(5000)
                        .setConnectionRequestTimeout(5000)
                        .setCookieSpec(CookieSpecs.STANDARD)
                        .build())
                .build();
    }

    public String getVersion() throws IOException {
        return send("/node/version", "version").asText();
    }

    public int getHeight() throws IOException {
        return send("/blocks/height", "height").asInt();
    }

    public long getBalance(String address) throws IOException {
        return send("/addresses/balance/" + address, "balance").asLong();
    }

    public long getBalance(String address, int confirmations) throws IOException {
        return send("/addresses/balance/" + address + "/" + confirmations, "balance").asLong();
    }

    public long getBalance(String address, String assetId) throws IOException {
        return Asset.isWaves(assetId)
                ? getBalance(address)
                : send("/assets/balance/" + address + "/" + assetId, "balance").asLong();
    }

    public Map<String, Long> getAssetDistribution(String assetId) throws IOException {
        String path = String.format("/assets/%s/distribution", assetId);
        HttpResponse r = exec(request(path));
        return parse(r, ASSET_DISTRIBUTION);
    }

    public AssetDistribution getAssetDistributionByHeight(String assetId, Integer height, Integer limit) throws IOException {
        String path = String.format("/assets/%s/distribution/%d/limit/%d", assetId, height, limit);
        HttpResponse r = exec(request(path));
        return parse(r, ASSET_DISTRIBUTION_BY_HEIGHT);
    }

    public AssetDistribution getAssetDistributionByHeight(String assetId, Integer height, Integer limit, String after) throws IOException {
        String path = String.format("/assets/%s/distribution/%d/limit/%d?after=%s", assetId, height, limit, after);
        HttpResponse r = exec(request(path));
        return parse(r, ASSET_DISTRIBUTION_BY_HEIGHT);
    }

    public List<AssetBalance> getAssetsBalance(String address) throws IOException {
        return wavesJsonMapper.convertValue(send("/assets/balance/" + address, "balances"), ASSET_BALANCE_LIST);
    }

    /**
     * Returns object by its ID.
     *
     * @param txId object ID
     * @return object object
     * @throws IOException if no object with the given ID exists
     */
    public Transaction getTransaction(String txId) throws IOException {
        return wavesJsonMapper.convertValue(send("/transactions/info/" + txId), Transaction.class);
    }

    public Map<String, Object> getTransactionData(String txId) throws IOException {
        return wavesJsonMapper.convertValue(send("/transactions/info/" + txId), TX_INFO);
    }

    /**
     * Returns transactions by address with limit.
     * @param address address
     * @param limit transactions limit
     * @return list of transactions
     * @throws IOException if something going wrong
     */
    public List<Transaction> getAddressTransactions(String address, int limit) throws IOException {
        return getAddressTransactions(address, limit, null);
    }

    /**
     * Returns transactions by address with limit after passed transaction id.
     * @param address address
     * @param limit transactions limit
     * @param after separate transaction id
     * @return list of transactions
     * @throws IOException if something going wrong
     */
    public List<Transaction> getAddressTransactions(String address, int limit, String after) throws IOException {
        String requestUrl = String.format("/transactions/address/%s/limit/%d", address, limit);
        if (after != null) {
            requestUrl += String.format("?after=%s", after);
        }
        return wavesJsonMapper.<List<List<Transaction>>>convertValue(send(requestUrl),new TypeReference<List<List<Transaction>>>(){}).get(0);
    }

    /**
     * Returns block at given height.
     *
     * @param height blockchain height
     * @return block object
     * @throws IOException if no block exists at the given height
     */
    public Block getBlock(int height) throws IOException {
        return wavesJsonMapper.convertValue(send("/blocks/at/" + height), Block.class);
    }

    /**
     * Returns block header at given height.
     *
     * @param height blockchain height
     * @return block object without transactions
     * @throws IOException if no block exists at the given height
     */
    public BlockHeader getBlockHeader(int height) throws IOException {
        return wavesJsonMapper.convertValue(send("/blocks/headers/at/" + height), BlockHeader.class);
    }

    /**
     * Returns last block header
     *
     * @return block object without transactions
     * @throws IOException if no block exists at the given height
     */
    public BlockHeader getLastBlockHeader() throws IOException {
        return wavesJsonMapper.convertValue(send("/blocks/headers/last"), BlockHeader.class);
    }

    /**
     * Returns seq of block headers
     *
     * @param from start block
     * @param to   end block
     * @return sequences of block objects without transactions
     * @throws IOException if no block exists at the given height
     */
    public List<BlockHeader> getBlockHeaderSeq(int from, int to) throws IOException {
        String path = String.format("/blocks/headers/seq/%s/%s", from, to);
        HttpResponse r = exec(request(path));
        return parse(r, BLOCK_HEADER_LIST);
    }

    /**
     * Returns block by its signature.
     *
     * @param signature block signature
     * @return block object
     * @throws IOException if no block with the given signature exists
     */
    public Block getBlock(String signature) throws IOException {
        return wavesJsonMapper.convertValue(send("/blocks/signature/" + signature), Block.class);
    }

    public boolean validateAddresses(String address) throws IOException {
        return send("/addresses/validate/" + address, "valid").asBoolean();
    }

    public String getAddrByAlias(String alias) throws IOException {
        return send("/alias/by-alias/" + alias, "address").textValue();
    }

    /**
     * Sends a signed object and returns its ID.
     *
     * @param tx signed object (as created by static methods in Transaction class)
     * @return Transaction ID
     * @throws IOException
     */
    public String send(Transaction tx) throws IOException {
        return parse(exec(request(tx)), "id").asText();
    }

    private JsonNode send(String path, String... key) throws IOException {
        return parse(exec(request(path)), key);
    }

    public String transfer(PrivateKeyAccount from, String recipient, long amount, long fee, String message) throws IOException {
        TransferTransactionV2 tx = Transactions.makeTransferTx(from, recipient, amount, null, fee, null, message);
        return send(tx);
    }

    public String transfer(PrivateKeyAccount from, String assetId, String recipient,
                           long amount, long fee, String feeAssetId, String message) throws IOException {
        TransferTransactionV2 tx = Transactions.makeTransferTx(from, recipient, amount, assetId, fee, feeAssetId, message);
        return send(tx);
    }

    public String lease(PrivateKeyAccount from, String recipient, long amount, long fee) throws IOException {
        LeaseTransaction tx = Transactions.makeLeaseTx(from, recipient, amount, fee);
        return send(tx);
    }

    public String cancelLease(PrivateKeyAccount account, byte chainId, String txId, long fee) throws IOException {
        return send(Transactions.makeLeaseCancelTx(account, chainId, txId, fee));
    }

    public String issueAsset(PrivateKeyAccount account, byte chainId, String name, String description, long quantity,
                             byte decimals, boolean reissuable, String script, long fee) throws IOException {
        return send(Transactions.makeIssueTx(account, chainId, name, description, quantity, decimals, reissuable, script, fee));
    }

    public String reissueAsset(PrivateKeyAccount account, byte chainId, String assetId, long quantity, boolean reissuable, long fee) throws IOException {
        return send(Transactions.makeReissueTx(account, chainId, assetId, quantity, reissuable, fee));
    }

    public String burnAsset(PrivateKeyAccount account, byte chainId, String assetId, long amount, long fee) throws IOException {
        return send(Transactions.makeBurnTx(account, chainId, assetId, amount, fee));
    }

    public String sponsorAsset(PrivateKeyAccount account, String assetId, long minAssetFee, long fee) throws IOException {
        return send(Transactions.makeSponsorTx(account, assetId, minAssetFee, fee));
    }

    public String alias(PrivateKeyAccount account, byte chainId, String alias, long fee) throws IOException {
        return send(Transactions.makeAliasTx(account, alias, chainId, fee));
    }

    public String massTransfer(PrivateKeyAccount from, String assetId, Collection<Transfer> transfers, long fee, String message) throws IOException {
        return send(Transactions.makeMassTransferTx(from, assetId, transfers, fee, message));
    }

    public String data(PrivateKeyAccount from, Collection<DataEntry<?>> data, long fee) throws IOException {
        return send(Transactions.makeDataTx(from, data, fee));
    }

    /**
     * Sets a validating script for an account.
     *
     * @param from    the account
     * @param script  script text
     * @param chainId chain ID
     * @param fee     object fee
     * @return object ID
     * @throws IOException if an error occurs
     * @see Account#MAINNET
     * @see Account#TESTNET
     */
    public String setScript(PrivateKeyAccount from, String script, byte chainId, long fee) throws IOException {
        return send(Transactions.makeScriptTx(from, compileScript(script), chainId, fee));
    }

    /**
     * Compiles a script.
     *
     * @param script the script to compile
     * @return compiled script, base64 encoded
     * @throws IOException if the script is not well formed or some other error occurs
     */
    public String compileScript(String script) throws IOException {
        if (script == null || script.isEmpty()) {
            return null;
        }
        HttpPost request = new HttpPost(uri.resolve("/utils/script/compile"));
        request.setEntity(new StringEntity(script));
        return parse(exec(request), "script").asText();
    }

    // Matcher transactions

    public String getMatcherKey() throws IOException {
        return parse(exec(request("/matcher"))).asText();
    }

    public Order createOrder(PrivateKeyAccount account, String matcherKey, AssetPair assetPair, Order.Type orderType,
                             long price, long amount, long expiration, long matcherFee) throws IOException {
        Order tx = Transactions.makeOrderTx(account, matcherKey, orderType, assetPair, price, amount, expiration, matcherFee);
        JsonNode tree = parse(exec(request(tx)));
        // fix order status
        ObjectNode message = (ObjectNode) tree.get("message");
        message.put("status", tree.get("status").asText());
        return wavesJsonMapper.treeToValue(tree.get("message"), Order.class);
    }

    public String cancelOrder(PrivateKeyAccount account, AssetPair assetPair, String orderId) throws IOException {
        ApiJson tx = Transactions.makeOrderCancelTx(account, assetPair, orderId);
        return parse(exec(request(tx)), "status").asText();
    }

    public String cancelOrdersbyPair(PrivateKeyAccount account, AssetPair assetPair) throws IOException {
        ApiJson tx = Transactions.makeOrderCancelTx(account, assetPair);
        return parse(exec(request(tx)), "status").asText();
    }

    public String cancelAllOrders(PrivateKeyAccount account) throws IOException {
        ApiJson tx = Transactions.makeOrderCancelTx(account);
        return parse(exec(request(tx)), "status").asText();
    }

    @Deprecated
    public String deleteOrder(PrivateKeyAccount account, AssetPair assetPair, String orderId) throws IOException {
        ApiJson tx = Transactions.makeDeleteOrder(account, assetPair, orderId);
        return parse(exec(request(tx)), "status").asText();
    }

    public OrderBook getOrderBook(AssetPair assetPair) throws IOException {
        String path = "/matcher/orderbook/" + assetPair.getAmountAsset() + '/' + assetPair.getPriceAsset();
        return parse(exec(request(path)), ORDER_BOOK);
    }

    public OrderStatusInfo getOrderStatus(String orderId, AssetPair assetPair) throws IOException {
        String path = "/matcher/orderbook/" + assetPair.getAmountAsset() + '/' + assetPair.getPriceAsset() + '/' + orderId;
        return parse(exec(request(path)), ORDER_STATUS);
    }

    public List<Order> getOrders(PrivateKeyAccount account) throws IOException {
        return getOrders(account, "/matcher/orderbook/" + Base58.encode(account.getPublicKey()));
    }

    public List<Order> getOrders(PrivateKeyAccount account, AssetPair market) throws IOException {
        return getOrders(account, String.format("/matcher/orderbook/%s/%s/publicKey/%s",
                market.getAmountAsset(), market.getPriceAsset(), Base58.encode(account.getPublicKey())));
    }

    public String getOrderHistorySignature(PrivateKeyAccount account, long timestamp) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(40);
        buf.put(account.getPublicKey()).putLong(timestamp);
        String signature = account.sign(buf.array());
        return signature;
    }

    private List<Order> getOrders(PrivateKeyAccount account, String path) throws IOException {
        long timestamp = System.currentTimeMillis();
        String signature = getOrderHistorySignature(account, timestamp);
        HttpResponse r = exec(request(path, "Timestamp", String.valueOf(timestamp), "Signature", signature));
        return parse(r, ORDER_LIST);
    }

    public Map<String, Long> getTradableBalance(AssetPair pair, String address) throws IOException {
        String path = String.format("/matcher/orderbook/%s/%s/tradableBalance/%s", pair.getAmountAsset(), pair.getPriceAsset(), address);
        HttpResponse r = exec(request(path));
        return parse(r, RESERVED);
    }

    public Map<String, Long> getReservedBalance(PrivateKeyAccount account) throws IOException {
        long timestamp = System.currentTimeMillis();
        ByteBuffer buf = ByteBuffer.allocate(40);
        buf.put(account.getPublicKey()).putLong(timestamp);
        String signature = account.sign(buf.array());
        HttpResponse r = exec(request(String.format("/matcher/balance/reserved/%s", Base58.encode(account.getPublicKey())), "Timestamp", String.valueOf(timestamp), "Signature", signature));
        return parse(r, RESERVED);
    }

    private <T> HttpUriRequest request(String path, String... headers) {
        HttpUriRequest req = new HttpGet(uri.resolve(path));
        for (int i = 0; i < headers.length; i += 2) {
            req.addHeader(headers[i], headers[i + 1]);
        }
        return req;
    }

    private HttpUriRequest request(ApiJson obj) throws JsonProcessingException {
        String endpoint;
        if (obj instanceof Transaction) {
            endpoint = "/transactions/broadcast";
        } else if (obj instanceof Order) {
            endpoint = "/matcher/orderbook";
        } else if (obj instanceof DeleteOrder) {
            DeleteOrder d = (DeleteOrder) obj;
            endpoint = "/matcher/orderbook/" + d.getAssetPair().getAmountAsset() + '/' + d.getAssetPair().getPriceAsset() + "/delete";
        } else if (obj instanceof CancelOrder) {
            CancelOrder co = (CancelOrder) obj;
            if (co.getAssetPair() == null) {
                endpoint = "/matcher/orderbook/cancel";
            } else
                endpoint = "/matcher/orderbook/" + co.getAssetPair().getAmountAsset() + '/' + co.getAssetPair().getPriceAsset() + "/cancel";
        } else {
            throw new IllegalArgumentException();
        }
        HttpPost request = new HttpPost(uri.resolve(endpoint));
        request.setEntity(new StringEntity(wavesJsonMapper.writeValueAsString(obj), ContentType.APPLICATION_JSON));
        return request;
    }

    private HttpResponse exec(HttpUriRequest request) throws IOException {
        HttpResponse r = client.execute(request);
        if (r.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            try {
                throw new IOException(EntityUtils.toString(r.getEntity()));
            } catch (JsonParseException e) {
                throw new RuntimeException("Server error " + r.getStatusLine().getStatusCode());
            }
        }
        return r;
    }

    private <T> T parse(HttpResponse r, TypeReference<T> ref) throws IOException {
        return wavesJsonMapper.readValue(r.getEntity().getContent(), ref);
    }

    private JsonNode parse(HttpResponse r, String... keys) throws IOException {
        JsonNode tree = wavesJsonMapper.readTree(r.getEntity().getContent());
        for (String key : keys) {
            tree = tree.get(key);
        }
        return tree;
    }
}


/*
 * Copyright 2014 Ruediger Moeller.
 *
 * 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.
 */
package org.nustaq.serialization.serializers;

import org.nustaq.offheap.bytez.onheap.HeapBytez;
import org.nustaq.offheap.structs.FSTStruct;
import org.nustaq.offheap.structs.unsafeimpl.FSTStructFactory;
import org.nustaq.serialization.FSTBasicObjectSerializer;
import org.nustaq.serialization.FSTClazzInfo;
import org.nustaq.serialization.FSTObjectInput;
import org.nustaq.serialization.FSTObjectOutput;

import java.io.IOException;

/**
 * Date: 09.11.13
 * Time: 22:06
 *
 * serializes struct classes
 *
 */
public class FSTStructSerializer extends FSTBasicObjectSerializer {

    public static boolean COMPRESS = true;

    /**
     * write the contents of a given object
     */
    @Override
    public void writeObject(FSTObjectOutput out, Object toWrite, FSTClazzInfo clzInfo, FSTClazzInfo.FSTFieldInfo referencedBy, int streamPosition) throws IOException {
        FSTStruct str = (FSTStruct) toWrite;
        if ( ! str.isOffHeap() ) {
            str = str.toOffHeap();
        }
        int byteSize = str.getByteSize();
        out.writeInt(byteSize);
        if ( COMPRESS ) {
            long base = str.___offset;
            int intsiz = byteSize/4;
            for ( int i=0; i<intsiz;i++ ) {
                int value = str.getInt();
                value = (value << 1) ^ (value >> 31);
                str.___offset+=4;
                while ((value & 0xFFFFFF80) != 0L) {
                    out.writeByte((value & 0x7F) | 0x80);
                    value >>>= 7;
                }
                out.writeByte(value & 0x7F);
            }
            int remainder = byteSize&3;
            for ( int i = 0; i < remainder; i++) {
                byte aByte = str.getByte();
                out.writeByte(aByte);
                str.___offset++;
            }
            str.___offset = base;
        } else {
            byte b[] = new byte[byteSize]; // fixme: cache threadlocal
            str.getBase().getArr(str.getOffset(), b, 0, byteSize);
            out.write( b, 0, byteSize);
        }
    }

    @Override
    public Object instantiate(Class objectClass, FSTObjectInput in, FSTClazzInfo serializationInfo, FSTClazzInfo.FSTFieldInfo referencee, int streamPosition) throws Exception {
        int len = in.readFInt();
        byte bytes[] = new byte[len];

        if ( COMPRESS ) {
            int intsiz = len/4;
            int count = 0;
            for ( int n=0; n<intsiz;n++ ) {
                int value = 0;
                int i = 0;
                int b;
                while (((b = in.readByte()) & 0x80) != 0) {
                    value |= (b & 0x7F) << i;
                    i += 7;
                }
                value = value | (b << i);
                int temp = (((value << 31) >> 31) ^ value) >> 1;
                value = temp ^ (value & (1 << 31));

                bytes[count++] = (byte) ((value >>> 0) & 0xFF);
                bytes[count++] = (byte) ((value >>>  8) & 0xFF);
                bytes[count++] = (byte) ((value >>> 16) & 0xFF);
                bytes[count++] = (byte) ((value >>> 24) & 0xFF);
            }
            int remainder = len&3;
            for ( int i = 0; i < remainder; i++) {
                bytes[count++] = in.readByte();
            }
        } else {
            in.read(bytes);
        }
        HeapBytez hb = new HeapBytez(bytes);
        return FSTStructFactory.getInstance().createStructWrapper(hb, 0);
    }
}


/*
 * Copyright 2014 Ruediger Moeller.
 *
 * 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.
 */
package org.nustaq.serialization.coders;

import org.nustaq.logging.FSTLogger;
import org.nustaq.serialization.FSTClazzInfo;
import org.nustaq.serialization.FSTClazzNameRegistry;
import org.nustaq.serialization.FSTConfiguration;
import org.nustaq.serialization.FSTDecoder;
import org.nustaq.serialization.util.FSTInputStream;
import org.nustaq.serialization.util.FSTUtil;

import java.io.IOException;
import java.io.InputStream;

/**
 * Default Coder used for serialization. Decodes a binary stream written with FSTStreamEncoder
 */
public class FSTStreamDecoder implements FSTDecoder {
    private static final FSTLogger LOGGER = FSTLogger.getLogger(FSTStreamDecoder.class);

    private FSTInputStream input;
    byte ascStringCache[];
    FSTConfiguration conf;
    public FSTClazzNameRegistry clnames;

    public FSTStreamDecoder(FSTConfiguration conf) {
        this.conf = conf;
        clnames = (FSTClazzNameRegistry) conf.getCachedObject(FSTClazzNameRegistry.class);
        if (clnames == null) {
            clnames = new FSTClazzNameRegistry(conf.getClassRegistry());
        } else {
            clnames.clear();
        }
    }

    @Override
    public void setConf(FSTConfiguration conf) {
        this.conf = conf;
        clnames = (FSTClazzNameRegistry) conf.getCachedObject(FSTClazzNameRegistry.class);
        if (clnames == null) {
            clnames = new FSTClazzNameRegistry(conf.getClassRegistry());
        } else {
            clnames.clear();
        }
    }

    public int ensureReadAhead(int bytes) {
        input.ensureReadAhead(bytes);
        return 0; // checking for eof too expensive ..
    }

    char chBufS[];
    char[] getCharBuf(int siz) {
        char chars[] = chBufS;
        if (chars == null || chars.length < siz) {
            chars = new char[Math.max(siz,15)];
            chBufS = chars;
        }
        return chars;
    }

    public String readStringUTF() throws IOException {
        int len = readFInt();
        char[] charBuf = getCharBuf(len*3);
        input.ensureReadAhead(len * 3);
        byte buf[] = input.buf;
        int count = input.pos;
        int chcount = 0;
        for (int i = 0; i < len; i++) {
            char head = (char) ((buf[count++] + 256) &0xff);
            if (head < 255) {
                charBuf[chcount++] = head;
            } else {
                int ch1 = ((buf[count++] + 256) &0xff);
                int ch2 = ((buf[count++] + 256) &0xff);
                charBuf[chcount++] = (char) ((ch1 << 0) + (ch2 << 8));
            }
        }
        input.pos = count;
        return new String(charBuf, 0, chcount);
    }

    public byte readObjectHeaderTag() throws IOException {
        return readFByte();
    }

    /**
     * len < 127 !!!!!
     *
     * @return
     * @throws java.io.IOException
     */
    @Override
    public String readStringAsc() throws IOException {
        int len = readFInt();
        if (ascStringCache == null || ascStringCache.length < len)
            ascStringCache = new byte[len];
        input.ensureReadAhead(len);
        System.arraycopy(input.buf, input.pos, ascStringCache, 0, len);
        input.pos += len;
        return new String(ascStringCache, 0, 0, len);
    }

    /**
     * assumes class header+len already read
     *
     * @param componentType
     * @param len
     * @return
     */
    @Override
    public Object readFPrimitiveArray(Object array, Class componentType, int len) {
        try {
            if (componentType == byte.class) {
                byte[] arr = (byte[]) array;
                ensureReadAhead(arr.length); // fixme: move this stuff to the stream !
                System.arraycopy(input.buf,input.pos,arr,0,len);
                input.pos += len;
                return arr;
            } else if (componentType == int.class) {
                final int[] arr = (int[]) array;
                readFIntArr(len, arr);
                return arr;
            } else if (componentType == long.class) {
                long[] arr = (long[]) array;
                readFLongArr(len, arr);
                return arr;
            } else if (componentType == char.class) {
                char[] arr = (char[]) array;
                for (int j = 0; j < len; j++) {
                    arr[j] = readFChar();
                }
                return arr;
            } else if (componentType == double.class) {
                double[] arr = (double[]) array;
                ensureReadAhead(arr.length * 8);
                for (int j = 0; j < len; j++) {
                    arr[j] = readFDouble();
                }
                return arr;
            } else if (componentType == short.class) {
                short[] arr = (short[]) array;
                ensureReadAhead(arr.length * 2);
                for (int j = 0; j < len; j++) {
                    arr[j] = readFShort();
                }
                return arr;
            } else if (componentType == float.class) {
                float[] arr = (float[]) array;
                ensureReadAhead(arr.length * 4);
                for (int j = 0; j < len; j++) {
                    arr[j] = readFFloat();
                }
                return arr;
            } else if (componentType == boolean.class) {
                boolean[] arr = (boolean[]) array;
                ensureReadAhead(arr.length);
                for (int j = 0; j < len; j++) {
                    arr[j] = readFByte() == 0 ? false : true;
                }
                return arr;
            } else {
                throw new RuntimeException("unexpected primitive type " + componentType.getName());
            }
        } catch (IOException e) {
            LOGGER.log(FSTLogger.Level.ERROR, "Failed to read primitive array", e);
            FSTUtil.<RuntimeException>rethrow(e);
        }
        return null;
    }

    // compressed version
    public void _readFIntArr(int len, int[] arr) throws IOException {
        ensureReadAhead(5 * len);
        final byte buf[] = input.buf;
        int count = input.pos;
        for (int j = 0; j < len; j++) {
            final byte head = buf[count++];
            // -128 = short byte, -127 == 4 byte
            if (head > -127 && head <= 127) {
                arr[j] = head;
                continue;
            }
            if (head == -128) {
                final int ch1 = (buf[count++]+256)&0xff;
                final int ch2 = (buf[count++]+256)&0xff;
                arr[j] = (short)((ch1 << 8) + (ch2 << 0));
                continue;
            } else {
                int ch1 = (buf[count++]+256)&0xff;
                int ch2 = (buf[count++]+256)&0xff;
                int ch3 = (buf[count++]+256)&0xff;
                int ch4 = (buf[count++]+256)&0xff;
                arr[j] = ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
            }
        }
        input.pos = count;
    }

    @Override // uncompressed version
    public void readFIntArr(int len, int[] arr) throws IOException {
        int bytelen = arr.length * 4;
        ensureReadAhead(bytelen);
        int count = input.pos;
        final byte buf[] = input.buf;
        for (int j = 0; j < len; j++) {
            int ch1 = (buf[count++] + 256) & 0xff;
            int ch2 = (buf[count++] + 256) & 0xff;
            int ch3 = (buf[count++] + 256) & 0xff;
            int ch4 = (buf[count++] + 256) & 0xff;
            arr[j] = (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
        }
        input.pos+=bytelen;
    }

    public void readFLongArr(int len, long[] arr) throws IOException {
        int bytelen = arr.length * 8;
        ensureReadAhead(bytelen);
        int count = input.pos;
        final byte buf[] = input.buf;
        for (int j = 0; j < len; j++) {
            long ch8 = (buf[count++] + 256) & 0xff;
            long ch7 = (buf[count++] + 256) & 0xff;
            long ch6 = (buf[count++] + 256) & 0xff;
            long ch5 = (buf[count++] + 256) & 0xff;
            long ch4 = (buf[count++] + 256) & 0xff;
            long ch3 = (buf[count++] + 256) & 0xff;
            long ch2 = (buf[count++] + 256) & 0xff;
            long ch1 = (buf[count++] + 256) & 0xff;
            arr[j] = ((ch1 << 56) + (ch2 << 48) + (ch3 << 40) + (ch4 << 32) + (ch5 << 24) + (ch6 << 16) + (ch7 << 8) + (ch8 << 0));
        }
        input.pos+=bytelen;
    }

    @Override
    public int readFInt() throws IOException {
        ensureReadAhead(5);
        final byte head = readFByte();
        // -128 = short byte, -127 == 4 byte
        if (head > -127 && head <= 127) {
            return head;
        }
        if (head == -128) {
            int count = input.pos;
            final byte buf[] = input.buf;
            int ch1 = (buf[count++] + 256) & 0xff;
            int ch2 = (buf[count++] + 256) & 0xff;
            input.pos = count;
            return (short) ((ch2 << 8) + (ch1 << 0));
        } else {
            int count = input.pos;
            final byte buf[] = input.buf;
            int ch1 = (buf[count++] + 256) & 0xff;
            int ch2 = (buf[count++] + 256) & 0xff;
            int ch3 = (buf[count++] + 256) & 0xff;
            int ch4 = (buf[count++] + 256) & 0xff;
            input.pos = count;
            int res = (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
            return res;
        }
    }

    @Override
    public double readFDouble() throws IOException {
        return Double.longBitsToDouble(readPlainLong());
    }

    /**
     * Reads a 4 byte float.
     */
    @Override
    public float readFFloat() throws IOException {
        return Float.intBitsToFloat(readPlainInt());
    }

    @Override
    public final byte readFByte() throws IOException {
        input.ensureReadAhead(1);
        if (input.pos > input.count) {
            throw new IOException("Failed to read the next byte");
        }
        return input.buf[input.pos++];
    }

    @Override
    public final int readIntByte() throws IOException {
        input.ensureReadAhead(1);
        if ( input.isFullyRead() )
            return -1;
        return input.buf[input.pos++] & 0xff;
    }

    @Override
    public long readFLong() throws IOException {
        input.ensureReadAhead(9);
        byte head = readFByte();
        // -128 = short byte, -127 == 4 byte
        if (head > -126 && head <= 127) {
            return head;
        }
        if (head == -128) {
            int count = input.pos;
            final byte buf[] = input.buf;
            int ch1 = (buf[count++] + 256) & 0xff;
            int ch2 = (buf[count++] + 256) & 0xff;
            input.pos = count;
            return (short) ((ch2 << 8) + (ch1 << 0));
        } else if (head == -127) {
            int count = input.pos;
            final byte buf[] = input.buf;
            int ch1 = (buf[count++] + 256) & 0xff;
            int ch2 = (buf[count++] + 256) & 0xff;
            int ch3 = (buf[count++] + 256) & 0xff;
            int ch4 = (buf[count++] + 256) & 0xff;
            input.pos = count;
            int res = (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
            return res;
        } else {
            ensureReadAhead(8);
            int count = input.pos;
            final byte buf[] = input.buf;
            long ch8 = (buf[count++] + 256) & 0xff;
            long ch7 = (buf[count++] + 256) & 0xff;
            long ch6 = (buf[count++] + 256) & 0xff;
            long ch5 = (buf[count++] + 256) & 0xff;
            long ch4 = (buf[count++] + 256) & 0xff;
            long ch3 = (buf[count++] + 256) & 0xff;
            long ch2 = (buf[count++] + 256) & 0xff;
            long ch1 = (buf[count++] + 256) & 0xff;
            input.pos = count;
            return ((ch1 << 56) + (ch2 << 48) + (ch3 << 40) + (ch4 << 32) + (ch5 << 24) + (ch6 << 16) + (ch7 << 8) + (ch8 << 0));
        }
    }

    @Override
    public char readFChar() throws IOException {
        input.ensureReadAhead(3);
        char head = (char) ((readFByte() + 256) & 0xff);
        // -128 = short byte, -127 == 4 byte
        if (head >= 0 && head < 255) {
            return head;
        }
        int ch1 = readFByte() & 0xff;
        int ch2 = readFByte() & 0xff;
        return (char)((ch1 << 0) + (ch2 << 8));
    }

    @Override
    public short readFShort() throws IOException {
        input.ensureReadAhead(3);
        int head = readFByte() & 0xff;
        if (head >= 0 && head < 255) {
            return (short) head;
        }
        int ch1 = readFByte() & 0xff;
        int ch2 = readFByte() & 0xff;
        return (short)((ch1 << 0) + (ch2 << 8));
    }

    private char readPlainChar() throws IOException {
        input.ensureReadAhead(2);
        int count = input.pos;
        final byte buf[] = input.buf;
        int ch2 = (buf[count++] + 256) & 0xff;
        int ch1 = (buf[count++] + 256) & 0xff;
        input.pos = count;
        return (char) ((ch1 << 8) + (ch2 << 0));
    }

    private short readPlainShort() throws IOException {
        input.ensureReadAhead(2);
        int count = input.pos;
        final byte buf[] = input.buf;
        int ch1 = (buf[count++] + 256) & 0xff;
        int ch2 = (buf[count++] + 256) & 0xff;
        input.pos = count;
        return (short) ((ch2 << 8) + (ch1 << 0));
    }

    @Override
    public int readPlainInt() throws IOException {
        input.ensureReadAhead(4);
        int count = input.pos;
        final byte buf[] = input.buf;
        int ch1 = (buf[count++] + 256) & 0xff;
        int ch2 = (buf[count++] + 256) & 0xff;
        int ch3 = (buf[count++] + 256) & 0xff;
        int ch4 = (buf[count++] + 256) & 0xff;
        input.pos = count;
        int res = (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
        return res;
    }

    private long readPlainLong() throws IOException {
        input.ensureReadAhead(8);
        int count = input.pos;
        final byte buf[] = input.buf;
        long ch8 = (buf[count++] + 256) & 0xff;
        long ch7 = (buf[count++] + 256) & 0xff;
        long ch6 = (buf[count++] + 256) & 0xff;
        long ch5 = (buf[count++] + 256) & 0xff;
        long ch4 = (buf[count++] + 256) & 0xff;
        long ch3 = (buf[count++] + 256) & 0xff;
        long ch2 = (buf[count++] + 256) & 0xff;
        long ch1 = (buf[count++] + 256) & 0xff;
        input.pos = count;
        return ((ch1 << 56) + (ch2 << 48) + (ch3 << 40) + (ch4 << 32) + (ch5 << 24) + (ch6 << 16) + (ch7 << 8) + (ch8 << 0));
    }

    @Override
    public byte[] getBuffer() {
        return input.buf;
    }

    @Override
    public int getInputPos() {
        return input.pos;
    }

    @Override
    public void moveTo(int position) {
        input.pos = position;
    }

    @Override
    public void reset() {
        input.reset();
        clnames.clear();
    }

    @Override
    public void setInputStream(InputStream in) {
        if ( input == null )
            input = new FSTInputStream(in);
        else
            input.initFromStream(in);
        clnames.clear();
    }

    public InputStream getInputStream() {
        return input;
    }

    @Override
    public void resetToCopyOf(byte[] bytes, int off, int len) {
        input.reset();
        input.ensureCapacity(len);
        input.count = len;
        System.arraycopy(bytes, off, input.buf, 0, len);
        clnames.clear();
        input.byteBacked = true;
    }

    @Override
    public void resetWith(byte[] bytes, int len) {
        clnames.clear();
        input.resetForReuse(bytes,len);
//        input.reset();
//        input.count = len;
//        input.buf = bytes;
//        input.pos = 0;
//        input.byteBacked = true;
    }

    @Override
    public FSTClazzInfo readClass() throws IOException, ClassNotFoundException {
        return clnames.decodeClass(this,conf);
    }

    @Override
    public Class classForName(String name) throws ClassNotFoundException {
        return clnames.classForName(name,conf);
    }
    @Override
    public void registerClass(Class possible) {
        clnames.registerClass(possible,conf);
    }
    @Override
    public void close() {
        conf.returnObject(clnames);
    }

    @Override
    public void skip(int n) {
        input.pos+=n;
    }

    @Override
    public void readPlainBytes(byte[] b, int off, int len) {
        input.ensureReadAhead(len);
        System.arraycopy(input.buf,input.pos,b,off,len);
        input.pos += len;
    }

    @Override
    public boolean isMapBased() {
        return false;
    }

    @Override
    public Object getDirectObject() // in case class already resolves to read object (e.g. mix input)
    {
        return null;
    }

    @Override
    public int getObjectHeaderLen()
    {
        return -1;
    }

    @Override
    public void consumeEndMarker() {}

    @Override
    public Class readArrayHeader() throws Exception {
        return readClass().getClazz();
    }

    @Override
    public void readExternalEnd() {
        // do nothing for direct encoding
    }

    @Override
    public boolean isEndMarker(String s) {
        return false;
    }

    @Override
    public int readVersionTag() throws IOException {
        return readFByte();
    }

    @Override
    public void pushBack(int bytes) {
        input.pos -= bytes;
    }

    @Override
    public void readArrayEnd(FSTClazzInfo clzSerInfo) {
    }

    @Override
    public void readObjectEnd() {
    }

    @Override
    public Object coerceElement(Class arrType, Object value) {
        return value;
    }

    @Override
    public int available() {
        input.ensureReadAhead(1);
        return input.available();
    }

    @Override
    public boolean inArray() {
        return false;
    }

    @Override
    public void startFieldReading(Object newObj) {

    }

    @Override
    public void endFieldReading(Object newObj) {

    }

}


/*
 * Copyright 2014 Ruediger Moeller.
 *
 * 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.
 */
package org.nustaq.serialization.coders;

import org.nustaq.offheap.bytez.BasicBytez;
import org.nustaq.offheap.bytez.onheap.HeapBytez;
import org.nustaq.serialization.*;
import org.nustaq.serialization.simpleapi.FSTBufferTooSmallException;
import org.nustaq.serialization.util.FSTUtil;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Created by ruedi on 08.11.2014.
 *
 * Enables serialization to arbitrary byte oriented memory
 *
 * no compression is applied, so writeFxx == writePlainXX
 */
public class FSTBytezEncoder implements FSTEncoder {

    private FSTConfiguration conf;

    private FSTClazzNameRegistry clnames;
    private BasicBytez buffout;
    private long pos;
    private byte[] ascStringCache;
    OutputStream outStream;
    boolean autoResize = true;

    public FSTBytezEncoder(FSTConfiguration conf, BasicBytez base) {
        if ( FSTConfiguration.isAndroid )
            throw new RuntimeException("not supported on android");
        this.conf = conf;
        buffout = base;
        clnames = (FSTClazzNameRegistry) conf.getCachedObject(FSTClazzNameRegistry.class);
        if ( clnames == null ) {
            clnames = new FSTClazzNameRegistry(conf.getClassRegistry());
        } else {
            clnames.clear();
        }
    }

    @Override
    public void setConf(FSTConfiguration conf) {
        this.conf = conf;
    }

    void writeFBooleanArr(boolean[] arr, int off, int len) throws IOException {
        ensureFree(len);
        buffout.setBoolean(pos,arr,off,len);
        pos += len;
    }

    public void writeFFloatArr(float[] arr, int off, int len) throws IOException {
        ensureFree(4*len);
        buffout.setFloat(pos, arr, off, len);
        pos += 4*len;
    }

    public void writeFDoubleArr(double[] arr, int off, int len) throws IOException {
        ensureFree(8*len);
        buffout.setDouble(pos,arr,off,len);
        pos += 8 * len;
    }

    public void writeFShortArr(short[] arr, int off, int len) throws IOException {
        ensureFree(2*len);
        buffout.setShort(pos, arr, off, len);
        pos += 2 * len;
    }

    public void writeFCharArr(char[] arr, int off, int len) throws IOException {
        ensureFree(2*len);
        buffout.setChar(pos,arr,off,len);
        pos += 2 * len;
    }

    void writeFIntArr(int[] arr, int off, int len) throws IOException {
        ensureFree(4*len);
        buffout.setInt(pos, arr, off, len);
        pos += 4 * len;
    }

    void writeFLongArr(long[] arr, int off, int len) throws IOException {
        ensureFree(8*len);
        buffout.setLong(pos, arr, off, len);
        pos += 8 * len;
    }

    /**
     * write prim array no len no tag
     *
     *
     * @param array
     * @throws IOException
     */
    public void writePrimitiveArray(Object array, int off, int len) throws IOException {
        Class<?> componentType = array.getClass().getComponentType();
        if ( componentType == byte.class ) {
            writeRawBytes((byte[]) array, off, len);
        } else
        if ( componentType == char.class ) {
            writeFCharArr((char[]) array, off, len);
        } else
        if ( componentType == short.class ) {
            writeFShortArr((short[]) array, off, len);
        } else
        if ( componentType == int.class ) {
            writeFIntArr((int[]) array, off, len);
        } else
        if ( componentType == double.class ) {
            writeFDoubleArr((double[]) array, off, len);
        } else
        if ( componentType == float.class ) {
            writeFFloatArr((float[]) array, off, len);
        } else
        if ( componentType == long.class ) {
            writeFLongArr((long[]) array, off, len);
        } else
        if ( componentType == boolean.class ) {
            writeFBooleanArr((boolean[]) array, off, len);
        } else {
            throw new RuntimeException("expected primitive array");
        }
    }

    /**
     * does not write length, just plain bytes
     *
     * @param array
     * @param length
     * @throws java.io.IOException
     */
    public void writeRawBytes(byte[] array, int start, int length) throws IOException {
        ensureFree((int) (pos+length));
        buffout.set(pos,array,start,length);
        pos += length;
    }

    char chBufS[];
    char[] getCharBuf(int siz) {
        char chars[] = chBufS;
        if (chars == null || chars.length < siz) {
            chars = new char[Math.max(siz, 15)];
            chBufS = chars;
        }
        return chars;
    }

    @Override
    public void writeStringUTF(String str) throws IOException {
        final int strlen = str.length();
        writeFInt(strlen);
        ensureFree(strlen*2);
        char c[] = getCharBuf(strlen);
        str.getChars(0,strlen,c,0);
        buffout.setChar(pos,c,0,strlen);
        pos += strlen*2;
    }

    @Override
    public void writeFShort(short c) throws IOException {
        writePlainShort(c);
    }

    public boolean writeAttributeName(FSTClazzInfo.FSTFieldInfo subInfo, Object value) {
        return false;
    }

    public boolean writeTag(byte tag, Object info, long somValue, Object toWrite, FSTObjectOutput oout) throws IOException {
        writeFByte(tag);
        return false;
    }

    @Override
    public void writeFChar(char c) throws IOException {
        writePlainChar(c);
    }

    @Override
    public final void writeFByte(int v) throws IOException {
        ensureFree(1);
        buffout.put(pos++, (byte) v);
    }

    @Override
    public void writeFInt(int anInt) throws IOException {
        writePlainInt(anInt);
    }

    @Override
    public void writeFLong(long anInt) throws IOException {
        writePlainLong(anInt);
    }

    /**
     * Writes a 4 byte float.
     */
    @Override
    public void writeFFloat(float value) throws IOException {
        writePlainInt(Float.floatToIntBits(value));
    }

    @Override
    public void writeFDouble(double value) throws IOException {
        writePlainLong(Double.doubleToLongBits(value));
    }

    @Override
    public int getWritten() {
        return (int) pos;
    }

    /**
     * close and flush to underlying stream if present. The stream is also closed
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        conf.returnObject(clnames);
    }

    @Override
    public void reset(byte[] out) {
        pos = 0;
        clnames.clear();
        if (out!=null)
            throw new RuntimeException("not implemented");
    }

    @Override
    public void skip(int i) {
        pos+=i;
    }

    /**
     * used to write uncompressed int (guaranteed length = 4) at a (eventually recent) position
     * @param position
     * @param v
     */
    @Override
    public void writeInt32At(int position, int v) {
        try {
            ensureFree( position+4);
        } catch (IOException e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
        buffout.putInt(position,v);
    }

    /**
     * if output stream is null, just encode into a byte array
     * @param outstream
     */
    @Override
    public void setOutstream(OutputStream outstream) {
        this.outStream = outstream;
    }

    /**
     * writes current buffer to underlying output and resets buffer.
     * @throws IOException
     */
    @Override
    public void flush() throws IOException {
        if ( outStream != null )
            outStream.write(getBuffer(), 0, (int) pos);
        pos = 0;
    }

    @Override
    public void ensureFree(int bytes) throws IOException {
        if ( buffout.length() <= pos+bytes) {
            if ( autoResize ) {
                BasicBytez newbytez = buffout.newInstance(Math.max(pos + bytes, Math.min(Integer.MAX_VALUE-10, buffout.length() * 2)));
                buffout.copyTo(newbytez, 0, 0, pos);
                // debug
                //            for ( int i = 0; i < pos; i++) {
                //                if ( buffout.get(i) != newbytez.get(i) ) {
                //                    throw new RuntimeException("error");
                //                }
                //            }
                buffout = newbytez;
            } else {
                throw FSTBufferTooSmallException.Instance;
            }
        }
    }

    // default is true
    public boolean isAutoResize() {
        return autoResize;
    }

    public void setAutoResize(boolean autoResize) {
        this.autoResize = autoResize;
    }

    @Override
    public byte[] getBuffer() {
        if (isPlainBAAccessible()) {
            return ((HeapBytez) buffout).asByteArray();
        }
        byte res[] = new byte[(int) pos];
        buffout.getArr(0,res,0, (int) pos);
        return res;
    }

    protected boolean isPlainBAAccessible() {
        return buffout.getClass() == HeapBytez.class && ((HeapBytez) buffout).getOffsetIndex() == 0;
    }

    public void registerClass(Class possible) {
        clnames.registerClass(possible,conf);
    }

    @Override
    public final void writeClass(Class cl) {
        try {
            clnames.encodeClass(this,cl);
        } catch ( IOException e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
    }

    @Override
    public final void writeClass(FSTClazzInfo clInf) {
        try {
            clnames.encodeClass(this, clInf);
        } catch ( IOException e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
    }

    private void writePlainLong(long v) throws IOException {
        ensureFree(8);
        buffout.putLong(pos,v);
        pos += 8;
    }

    private void writePlainShort(int v) throws IOException {
        ensureFree(2);
        buffout.putShort(pos, (short) v);
        pos += 2;
    }

    private void writePlainChar(int v) throws IOException {
        ensureFree(2);
        buffout.putChar(pos, (char) v);
        pos += 2;
    }

    private void writePlainInt(int v) throws IOException {
        ensureFree(4);
        buffout.putInt(pos, v);
        pos += 4;
    }

    public void externalEnd(FSTClazzInfo clz) {
    }

    @Override
    public boolean isWritingAttributes() {
        return false;
    }

    public boolean isPrimitiveArray(Object array, Class<?> componentType) {
        return componentType.isPrimitive();
    }

    public boolean isTagMultiDimSubArrays() {
        return false;
    }

    @Override
    public void writeVersionTag(int version) throws IOException {
        writeFByte(version);
    }

    @Override
    public boolean isByteArrayBased() {
        return false || isPlainBAAccessible();
    }

    @Override
    public void writeArrayEnd() {

    }

    @Override
    public void writeFieldsEnd(FSTClazzInfo serializationInfo) {

    }

    @Override
    public FSTConfiguration getConf() {
        return conf;
    }

}


/*
 * Copyright 2014 Ruediger Moeller.
 *
 * 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.
 */
package org.nustaq.serialization.simpleapi;

import org.nustaq.offheap.bytez.malloc.MallocBytez;
import org.nustaq.serialization.*;
import org.nustaq.serialization.coders.FSTBytezDecoder;
import org.nustaq.serialization.coders.FSTBytezEncoder;

import java.io.IOException;

/**
 * Created by ruedi on 09.11.14.
 *
 * enables zero copy encoding to offheap memory. The encoding is platform dependent (endianess) and
 * no attemps on compression are made.
 *
 * Use case: messaging, offheap en/decoding, tmp preservation of state
 * NOT thread safe
 *
 * Future version may choose to operate on DirectByteBuffer in case unsafe class vanishes
 *
 * Do not confuse this with a stream. Each single writeObject is an isolated operation,
 * so restoring of references inside an object graph only happens for refs inside the object graph
 * given to writeObject.
 *
 * ***********************************************************************
 * USE ONLY IF DEFAULTCODER (no unsafe) HAS BEEN PROVEN TO SLOW YOU DOWN.
 * ***********************************************************************
 *
 * Note this does not satisfy the FSTCoder interface. Its purely targeted to directly read/write native memory
 * allocates using unsafe.
 */
public class OffHeapCoder {

    protected FSTConfiguration conf;
    MallocBytez writeTarget;
    MallocBytez readTarget;
    FSTObjectOutput out;
    FSTObjectInput in;

    public OffHeapCoder() {
        this(true);
    }
    public OffHeapCoder(boolean sharedRefs) {
        conf = FSTConfiguration.createUnsafeBinaryConfiguration();
        conf.setShareReferences(sharedRefs);
        writeTarget = new MallocBytez(0l,0);
        readTarget = new MallocBytez(0l,0);
        conf.setStreamCoderFactory(new FSTConfiguration.StreamCoderFactory() {
            @Override
            public FSTEncoder createStreamEncoder() {
                FSTBytezEncoder fstBytezEncoder = new FSTBytezEncoder(conf, writeTarget);
                fstBytezEncoder.setAutoResize(false);
                return fstBytezEncoder;
            }

            @Override
            public FSTDecoder createStreamDecoder() {
                return new FSTBytezDecoder(conf,readTarget);
            }

            ThreadLocal input = new ThreadLocal();
            ThreadLocal output = new ThreadLocal();
            @Override
            public ThreadLocal getInput() {
                return input;
            }

            @Override
            public ThreadLocal getOutput() {
                return output;
            }

        });
        if (sharedRefs) {
            out = conf.getObjectOutput();
            in = conf.getObjectInput();
        } else {
            out = new FSTObjectOutputNoShared(conf);
            in = new FSTObjectInputNoShared(conf);
        }
    }

    /**
     * throw
     * @param preregister
     */
    public OffHeapCoder( boolean sharedRefs, Class ... preregister ) {
        this(sharedRefs);
        conf.registerClass(preregister);
    }

    public OffHeapCoder( Class ... preregister ) {
        this(true);
        conf.registerClass(preregister);
    }

    /**
     * throws FSTBufferTooSmallException in case object does not fit into given range
     *
     * @param o
     * @param address
     * @param availableSize
     * @throws IOException
     * @return number of bytes written to the memory region
     */
    public int toMemory(Object o, long address, int availableSize) throws IOException {
        out.resetForReUse();
        writeTarget.setBase(address, availableSize);
        out.writeObject(o);
        int written = out.getWritten();
        return written;
    }

    public Object toObject(long address, int availableSize) throws IOException, ClassNotFoundException {
        in.resetForReuse(null);
        readTarget.setBase(address,availableSize);
        Object o = in.readObject();
        return o;
    }

}


/*
 * Copyright 2014 Ruediger Moeller.
 *
 * 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.
 */

package org.nustaq.kson;

import org.nustaq.serialization.FSTClazzInfo;
import org.nustaq.serialization.FSTConfiguration;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

/**
 *

 * Date: 20.12.13
 * Time: 12:03
 *

 * Created by ruedi on 07.08.2014.
 */
public class KsonSerializer {
    protected KsonCharOutput out;
    protected KsonTypeMapper mapper;
    protected boolean pretty = true;
    protected boolean writeNull = false;
    protected FSTConfiguration conf;

    public KsonSerializer(KsonCharOutput out, KsonTypeMapper mapper, FSTConfiguration conf) {
        this.out = out;
        this.mapper = mapper;
        this.conf = conf;
    }

    public void writeObject(Object o) throws Exception {
        writeObjectInternal(null, null, o, 0);
    }

    protected void writeObjectInternal(Class expectedClass, Class expectedValueClass, Object o, int indent) throws Exception {
        if (o == null) {
            if (indent >= 0) writeIndent(indent);
            out.writeString("null");
            if (indent >= 0) writeln();
            return;
        }
        if (o instanceof Character) {
            if (indent >= 0) writeIndent(indent);
            final char ch = ((Character) o).charValue();
            if (ch <128 && ch > 32)
                out.writeString(o.toString());
            else
                out.writeString(Integer.toString(ch));
            if (indent >= 0) writeln();
        } else if (o instanceof Number || o instanceof Boolean) {
            if (indent >= 0) writeIndent(indent);
            out.writeString(o.toString());
            if (indent >= 0) writeln();
        } else if (o instanceof String) {
            if (indent >= 0) writeIndent(indent);
            writeString((String) o);
            if (indent >= 0) writeln();
        } else if (o instanceof Map) {
            Map map = (Map) o;
            writeIndent(indent);
            out.writeString("{");
            writeln();
            for (Iterator iterator = map.keySet().iterator(); iterator.hasNext(); ) {
                Object next = iterator.next();
                Object value = map.get(next);
                boolean valueSingleLine = isSingleLine(null, value);
                boolean keySingleLine = isSingleLine(null, next);
                if (keySingleLine) {
                    writeIndent(indent + 1);
                    if ( next instanceof String ) {
                        writeKey((String) next);
                    } else
                        writeObjectInternal(expectedClass, null, next, -1);
                    out.writeString(" : ");
                    if (!valueSingleLine)
                        writeln();
                } else {
                    writeObjectInternal(expectedClass, null, next, indent + 1);
                    writeIndent(indent + 1);
                    out.writeString(":");
                    if (!valueSingleLine)
                        writeln();
                }
                if (valueSingleLine) {
                    out.writeChar(' ');
                    writeObjectInternal(expectedValueClass, null, value, -1);
                    if (iterator.hasNext())
                        writeListSep();
                    writeln();
                } else {
                    writeObjectInternal(expectedValueClass, null, value, indent + 1);
                    if (iterator.hasNext())
                        writeListSep();
                }
            }
            writeIndent(indent);
            out.writeChar('}');
            writeln();
        } else if (o instanceof Collection) {
            Collection coll = (Collection) o;
            writeIndent(indent);
            writeListStart();
            writeln();
            for (Iterator iterator = coll.iterator(); iterator.hasNext(); ) {
                Object next = iterator.next();
                writeObjectInternal(expectedClass, null, next, indent + 1);
                if (iterator.hasNext())
                    writeListSep();
            }
            writeIndent(indent);
            writeListEnd();
            writeln();
        } else if (o.getClass().isArray()) {
            writeIndent(indent);
            writeListStart();
            int len = Array.getLength(o);
            boolean lastWasSL = true;
            Class expect = o.getClass().getComponentType();
            for (int ii = 0; ii < len; ii++) {
                Object val = Array.get(o, ii);
                if (lastWasSL && (isSingleLine(null, val)||isSingleLineCLazz(expect))) {
                    writeObjectInternal(expect, null, val, -1);
                    out.writeChar(' ');
                    if (ii < len-1)
                        writeListSep();
                } else {
                    if (ii == 0)
                        writeln();
                    writeObjectInternal(expect, null, val, indent + 1);
                    if (ii < len-1)
                        writeListSep();
                    lastWasSL = false;
                }
            }
            if (!lastWasSL)
                writeIndent(indent);
            writeListEnd();
            writeln();
        } else {

            writeIndent(indent);
            writeClazzTag(expectedClass, o);
            writeln();

            FSTClazzInfo clInfo = conf.getCLInfoRegistry().getCLInfo(o.getClass(), conf);
            FSTClazzInfo.FSTFieldInfo[] fieldInfo = clInfo.getFieldInfo();

            for (int i = 0; i < fieldInfo.length; i++) {
                FSTClazzInfo.FSTFieldInfo fstFieldInfo = fieldInfo[i];
                Class expectedKey = Kson.fumbleOutGenericKeyType(fstFieldInfo.getField());
                Class expectedValue = Kson.fumbleOutGenericValueType(fstFieldInfo.getField());
                Object fieldValue = fstFieldInfo.getField().get(o);
                //              fieldValue = mapper.coerceWriting(fieldValue);
                if (!isNullValue(fstFieldInfo, fieldValue) || writeNull) {
                    writeIndent(indent + 1);
                    final String name = fstFieldInfo.getName();
                    writeKey(name);
                    out.writeChar(':');
                    if (isSingleLine(fstFieldInfo, fieldValue)) {
                        out.writeString(" ");
                        writeObjectInternal(expectedKey, expectedValue, fieldValue, 0);
                        writeListSep();
                    } else {
                        writeln();
                        writeObjectInternal(expectedKey, expectedValue, fieldValue, indent + 2);
                        writeListSep();
                    }
                }
            }
            removeLastListSep();
            writeIndent(indent);
            out.writeChar('}');
            writeln();
        }
    }

    /**
     * called when writing a key of an key:value inside an object or map
     * @param name
     */
    protected void writeKey(String name) {
        out.writeString(name);
    }

    /**
     * determines classname tagging. Overrifing can enforce class tags always or (JSon) write as
     * special attribute
     * @param expectedClass
     * @param o
     */
    protected void writeClazzTag(Class expectedClass, Object o) {
        if (expectedClass == o.getClass()) {
            out.writeString("{");
        } else {
            String stringForType = mapper.getStringForType(o.getClass());
            out.writeString(stringForType + " {");
        }
    }

    protected void writeListEnd() {
        out.writeChar(']');
    }

    protected void writeListStart() {
        out.writeString("[ ");
    }

    // quirksmode: true
    protected void removeLastListSep() {

    }

    protected void writeListSep() {

    }

    private boolean isSingleLine(FSTClazzInfo.FSTFieldInfo fstFieldInfo, Object fieldValue) {
        if (fstFieldInfo == null) {
            if (fieldValue instanceof Class) { // now that's dirty ..
                Class clz = (Class) fieldValue;
                return isSingleLineCLazz(clz);
            }
            return fieldValue==null || isSingleLineCLazz(fieldValue.getClass());
        }
        if (fstFieldInfo.isArray() && isSingleLine(null, fstFieldInfo.getType().getComponentType())) {
            return true;
        }
        return (fieldValue==null || isSingleLineCLazz(fieldValue.getClass())) || isSingleLineCLazz(fstFieldInfo.getType());
    }

    private boolean isSingleLineCLazz(Class clz) {
        return String.class.isAssignableFrom(clz) || Number.class.isAssignableFrom(clz) || clz.isPrimitive() || clz == Boolean.class || clz == Character.class;
    }

    static Character zeroC = Character.valueOf((char) 0);

    private boolean isNullValue(FSTClazzInfo.FSTFieldInfo fstFieldInfo, Object fieldValue) {
        if (fieldValue==null)
            return true;
        if (writeNull)
            return false;
        if (fstFieldInfo != null ) {
            if (fstFieldInfo.getType().isPrimitive()) {
                if (fieldValue instanceof Number) {
                    return ((Number) fieldValue).doubleValue() == 0.0;
                }
                return fieldValue.equals(zeroC) || fieldValue.equals(Boolean.FALSE);
            }
        }
        return false;
    }

    protected void writeln() {
        if (pretty)
            out.writeChar('\n');
    }

    public void writeString(String string) {
        if (string == null) {
            out.writeString("null");
            return;
        }
        if (string.length() == 0) {
            out.writeString("\"\"");
            return;
        }

        char b;
        char c = 0;
        int i;
        int len = string.length();
        String t;
        boolean ws = shouldQuote(string);

        if (ws)
            out.writeChar('\"');
        for (i = 0; i < len; i += 1) {
            b = c;
            c = string.charAt(i);
            switch (c) {
                case '\\':
                case '"':
                    out.writeChar('\\');
                    out.writeChar(c);
                    break;
                case '/':
                    if (b == '<') {
                        out.writeChar('\\');
                    }
                    out.writeChar(c);
                    break;
                case '\b':
                    out.writeString("\\b");
                    break;
                case '\t':
                    out.writeString("\\t");
                    break;
                case '\n':
                    out.writeString("\\n");
                    break;
                case '\f':
                    out.writeString("\\f");
                    break;
                case '\r':
                    out.writeString("\\r");
                    break;
                default:
                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0') ||
                            (c >= '\u2000' && c < '\u2100')) {
                        t = "000" + Integer.toHexString(c);
                        out.writeString("\\u" + t.substring(t.length() - 4));
                    } else {
                        out.writeChar(c);
                    }
            }
        }
        if (ws)
            out.writeChar('"');
    }

    protected boolean shouldQuote(String string) {
        boolean ws = false;
        for (int ii = 0; ii < string.length(); ii++) {
            final char ch = string.charAt(ii);
            if (ii==0 && !Character.isLetter(ch) )
                return true;
            if (!Character.isLetterOrDigit(ch) && ch != '_' && ch != '-' && ch != '(' && ch != ')' && ch != '[' && ch != ']' && ch != '.') {
                ws = true;
                break;
            }
        }
        return ws;
    }

    protected void writeIndent(int indent) {
        if (pretty) {
            for (int i = 0; i < indent * 2; i++) {
                out.writeChar(' ');
            }
        }
    }

    public boolean isWriteNull() {
        return writeNull;
    }

    public void setWriteNull(boolean writeNull) {
        this.writeNull = writeNull;
    }

    public void writeObject(Object o, Class aClass) throws Exception {
        writeObjectInternal(aClass, null, o, 0);
    }
}