package com.derbysoft.spitfire.fastjson;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang.time.StopWatch;
import org.junit.Test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.derbysoft.spitfire.fastjson.dto.AvailGuaranteeDTO;
import com.derbysoft.spitfire.fastjson.dto.AvailRoomStayDTO;
import com.derbysoft.spitfire.fastjson.dto.CancelPenaltyType;
import com.derbysoft.spitfire.fastjson.dto.CancelPolicyDTO;
import com.derbysoft.spitfire.fastjson.dto.CardCode;
import com.derbysoft.spitfire.fastjson.dto.ChargeItemDTO;
import com.derbysoft.spitfire.fastjson.dto.ChargeType;
import com.derbysoft.spitfire.fastjson.dto.ChargeUnit;
import com.derbysoft.spitfire.fastjson.dto.Currency;
import com.derbysoft.spitfire.fastjson.dto.DateRangeDTO;
import com.derbysoft.spitfire.fastjson.dto.FreeMealDTO;
import com.derbysoft.spitfire.fastjson.dto.FreeMealType;
import com.derbysoft.spitfire.fastjson.dto.GenericRS;
import com.derbysoft.spitfire.fastjson.dto.GuaranteeType;
import com.derbysoft.spitfire.fastjson.dto.HotelAvailRS;
import com.derbysoft.spitfire.fastjson.dto.HotelAvailRoomStayDTO;
import com.derbysoft.spitfire.fastjson.dto.HotelRefDTO;
import com.derbysoft.spitfire.fastjson.dto.LanguageType;
import com.derbysoft.spitfire.fastjson.dto.MealsIncludedDTO;
import com.derbysoft.spitfire.fastjson.dto.MealsIncludedType;
import com.derbysoft.spitfire.fastjson.dto.PaymentType;
import com.derbysoft.spitfire.fastjson.dto.ProviderChainDTO;
import com.derbysoft.spitfire.fastjson.dto.RateDTO;
import com.derbysoft.spitfire.fastjson.dto.RatePlanDTO;
import com.derbysoft.spitfire.fastjson.dto.ResponseHeader;
import com.derbysoft.spitfire.fastjson.dto.RoomRateDTO;
import com.derbysoft.spitfire.fastjson.dto.RoomTypeDTO;
import com.derbysoft.spitfire.fastjson.dto.SimpleAmountDTO;
import com.derbysoft.spitfire.fastjson.dto.TPAExtensionsDTO;
import com.derbysoft.spitfire.fastjson.dto.UniqueIDDTO;
import com.derbysoft.spitfire.fastjson.dto.UniqueIDType;

//import com.derbysoft.spitfire.fastjson.dto.*;

public class TestFastJson {

    private static final int TIMES       = 10000;
    private static final int STAYS_COUNT = 10;

    public void f_testF() {
        Generic<String> q = new Generic<String>();
        byte[] text = JSON.toJSONBytes(q, SerializerFeature.WriteClassName);
        JSON.parseObject(text, Generic.class);
    }

    @SuppressWarnings("unchecked")
    public void f_test() throws Exception {

        String text = JSON.toJSONString(createTest(), SerializerFeature.WriteClassName);
        System.out.println(text.length());
        System.out.println(text);
        System.out.println("serialize finished");
        GenericRS<HotelAvailRS> o = (GenericRS<HotelAvailRS>) JSON.parseObject(text, GenericRS.class);

        System.out.println(o);
    }

    public void testFP() throws IOException {
        Generic<String> q = new Generic<String>();
        for (int x = 0; x < STAYS_COUNT; ++x) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            for (int i = 0; i < TIMES; ++i) {
                jsonSerialize(q);
            }
            stopWatch.stop();

            System.out.println("JSON serialize:" + stopWatch.getTime());

            stopWatch.reset();
            stopWatch.start();
            for (int i = 0; i < TIMES; ++i) {
                javaSerialize(q);
            }
            stopWatch.stop();
            System.out.println("JAVA serialize:" + stopWatch.getTime());
            System.out.println();
        }
    }

    private <T> void jsonSerialize(T t) throws IOException {
        // String text = JSON.toJSONString(t, SerializerFeature.WriteClassName, SerializerFeature.PrettyFormat);
        // System.out.println(text);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        SerializeWriter out = new SerializeWriter(SerializerFeature.WriteClassName);
        // SerializeWriter out = new SerializeWriter();
        JSONSerializer serializer = new JSONSerializer(out);
        serializer.write(t);
        out.writeTo(os, "UTF-8");
        os.toByteArray();
        //System.out.println(JSON.toJSONString(t, SerializerFeature.WriteClassName, SerializerFeature.PrettyFormat));
        //System.out.println("json " + os.toByteArray().length);
    }

    private <T> void javaSerialize(T t) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(t);
        // System.out.println("java " + os.toByteArray().length);
    }

    @SuppressWarnings("unchecked")
    private <T> T jsonDeserialize(byte[] bytes, Class<T> clazz) throws IOException {
        return (T) JSON.parseObject(bytes, clazz);
    }

    @SuppressWarnings("unchecked")
    private <T> T javaDeserialize(byte[] bytes) throws IOException, ClassNotFoundException {
        ByteArrayInputStream is = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(is);
        return (T) ois.readObject();
    }

    @Test
    public void testSerializePerformance() throws IOException {
        Object obj = createTest();

        for (int x = 0; x < 20; ++x) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            for (int i = 0; i < TIMES; ++i) {
                jsonSerialize(obj);
            }
            stopWatch.stop();

            System.out.println("JSON serialize:" + stopWatch.getTime());

            stopWatch.reset();
            stopWatch.start();
            for (int i = 0; i < TIMES; ++i) {
                javaSerialize(obj);
            }
            stopWatch.stop();
            System.out.println("JAVA serialize:" + stopWatch.getTime());
            System.out.println();
        }
    }

    public void testDeserializePerformance() throws IOException, ClassNotFoundException {
        Object obj = createTest();
        byte[] bytes = JSON.toJSONBytes(obj, SerializerFeature.WriteClassName);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(obj);
        byte[] javaBytes = os.toByteArray();

        System.out.println(bytes.length);

        for (int x = 0; x < 20; ++x) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            for (int i = 0; i < TIMES; ++i) {
                // ByteArrayInputStream is = new ByteArrayInputStream(bytes);
                Object o = jsonDeserialize(bytes, GenericRS.class);
                o.getClass();
            }
            stopWatch.stop();

            System.out.println("JSON deserialize:" + stopWatch.getTime());

            stopWatch.reset();

            stopWatch.start();
            for (int i = 0; i < TIMES; ++i) {
                Object o = javaDeserialize(javaBytes);
                o.getClass();
            }
            stopWatch.stop();

            System.out.println("JAVA deserialize:" + stopWatch.getTime());
            System.out.println();
        }

    }

    public GenericRS<HotelAvailRS> createTest() {
        GenericRS<HotelAvailRS> rs = new GenericRS<HotelAvailRS>();
        rs.setHeader(new ResponseHeader("dsfsdfsd"));
        HotelAvailRS hotelAvailRS = createExpectedHotelAvailRS();
        TPAExtensionsDTO extensions = new TPAExtensionsDTO();
        extensions.getElements().put("dfd", "Dfdf");
        hotelAvailRS.setTpaExtensions(extensions);
        rs.setPayload(hotelAvailRS);
        return rs;
    }

    // private static final String HOTEL_DESC = "foo hotel desc";
    private static final String       ROOM_TYPE_CODE            = "foo room type code";
    private static final String       ROOM_TYPE_NAME            = "foo room type name";
    private static final String       RATE_PLAN_CODE            = "foo rate plan code";
    private static final String       RATE_PLAN_NAME            = "rate plan name";
    private static final Integer      ROOM_COUNT                = 2;

    // private static final Date CANCEL_POLICY_DEAD_LINE = DateUtils.parseUseDefaultFormat("2008-09-01");
    private static final String       CANCEL_POLICY_DESCRIPTION = "foo cancel policy description";

    private static final String       GUARANTEE_DESCRIPTION     = "foo guarantee description";
    // private static final String CARD_NUMBER = "foo card number";
    // private static final String CARD_HOLDER_NAME = "foo card holder name";
    // private static final String CARD_SERIES_CODE = "foo card series code";
    // private static final Date CARD_EXPIRE_DATE = DateUtils.parseUseDefaultFormat("2010-12-31");
    private static final Currency     CURRENCY                  = Currency.CNY;

    private static final PaymentType  PAYMENT_TYPE_POA          = PaymentType.POA;
    // private static final int DAY_COUNT = 4;
    // private static final String[] CHECKIN_DATES = {"2008-08-25", "2008-08-26", "2008-08-27", "2008-08-28"};
    // private static final String[] CHECKOUT_DATES = {"2008-08-26", "2008-08-27", "2008-08-28", "2008-08-29"};
    // private static final BigDecimal[] AMOUNT_AFTER_TAXS
    // = {new BigDecimal(800), new BigDecimal(800), new BigDecimal(800), new BigDecimal(900)};
    private static final BigDecimal[] AMOUNT_BEFORE_TAXS        = { new BigDecimal(750), new BigDecimal(750),
            new BigDecimal(760), new BigDecimal(880)           };
    private static final BigDecimal   AMOUNT_TAX                = new BigDecimal(50);
    private static final BigDecimal   SERVICE_CHARGE_AMOUNT     = new BigDecimal(10);
    private static final String       TAX_DESC                  = "foo tax desc";
    private static final String       SERVICE_CHARGE_DESC       = "foo repository charge desc";

    private static final String       PROVIDER_CODE             = "hilton";
    private static final String       HOTEL_NAME                = "foo hotel name";
    private static final String       HOTEL_CODE                = "foo hotel code";
    private static final LanguageType LANGUAGE_TYPE_CN          = LanguageType.ZH_CN;
    // private static final String TASK_ID = "task id";
    private static final boolean      NEED_GUARANTEE            = true;
    private static final CardCode     VISA                      = CardCode.VISA;
    // private static final String CARD_CODE = VISA.getCode();

    private static final int          NUMBER_ZERO               = 0;
    private static final int          NUMBER_ONE                = 1;
    private static final int          NUMBER_TWO                = 2;
    private static final int          NUMBER_THREE              = 3;
    
    private static AtomicLong seed = new AtomicLong();

    private HotelAvailRS createExpectedHotelAvailRS() {
        HotelAvailRS payLoad = new HotelAvailRS();
        payLoad.setHotelAvailRoomStay(createExpectedHotelAvailRoomStay());
        return payLoad;
    }

    private HotelAvailRoomStayDTO createExpectedHotelAvailRoomStay() {
        HotelAvailRoomStayDTO hotelAvailRoomStay = new HotelAvailRoomStayDTO();
        hotelAvailRoomStay.setHotelRef(createExpectedHotelRef());
        hotelAvailRoomStay.setRoomStays(createExpectedRoomStays());
        return hotelAvailRoomStay;
    }

    private List<AvailRoomStayDTO> createExpectedRoomStays() {
        ArrayList<AvailRoomStayDTO> roomStays = new ArrayList<AvailRoomStayDTO>();
        for (int i = 0; i < STAYS_COUNT; ++i) {
            AvailRoomStayDTO roomStay = new AvailRoomStayDTO();
            roomStay.setLanguageType(LANGUAGE_TYPE_CN);
            roomStay.setRoomType(createExpectedRoomType());
            roomStay.setRatePlan(createExpectedRatePlan());
            roomStay.setQuantity(ROOM_COUNT);
            roomStay.setRoomRate(createExpectedRoomRate());
            roomStay.setProviderChain(createExpectedProviderChain());
            roomStays.add(roomStay);
        }

        return roomStays;
    }

    private ProviderChainDTO createExpectedProviderChain() {
        ProviderChainDTO providerChain = new ProviderChainDTO();
        List<UniqueIDDTO> providers = new ArrayList<UniqueIDDTO>();
        UniqueIDDTO provider = new UniqueIDDTO(PROVIDER_CODE, UniqueIDType.HOTEL);
        provider.setCompanyName(PROVIDER_CODE);
        providers.add(provider);
        providerChain.setProviders(providers);
        return providerChain;
    }

    private List<AvailGuaranteeDTO> createExpectedAvailGuarantee() {
        List<AvailGuaranteeDTO> availGuarantees = new ArrayList<AvailGuaranteeDTO>();
        AvailGuaranteeDTO availGuaranteeDTO = new AvailGuaranteeDTO();
        availGuaranteeDTO.setGuaranteeType(GuaranteeType.CreditCard);
        availGuaranteeDTO.setCardCode(VISA);
        availGuaranteeDTO.setTpaExtensions(createExpectedEpaExtensions());
        availGuarantees.add(availGuaranteeDTO);
        return availGuarantees;
    }

    private TPAExtensionsDTO createExpectedEpaExtensions() {
        TPAExtensionsDTO tpaExtensions = new TPAExtensionsDTO();
        tpaExtensions.setElement("description", GUARANTEE_DESCRIPTION);
        return tpaExtensions;
    }

    private CancelPolicyDTO createExpectedCancelPolicy() {
        CancelPolicyDTO cancelPolicyDTO = new CancelPolicyDTO();
        cancelPolicyDTO.setCancelPenaltyType(CancelPenaltyType.UNKNOWN);
        cancelPolicyDTO.setDeadline("16:00");
        cancelPolicyDTO.setDescription(CANCEL_POLICY_DESCRIPTION);
        return cancelPolicyDTO;
    }

    private RoomRateDTO createExpectedRoomRate() {
        RoomRateDTO roomRate = new RoomRateDTO();
        List<RateDTO> rates = new ArrayList<RateDTO>();
        rates.add(createExpectedFirstDayRate());
        rates.add(createExpectedSecondDayRate());
        rates.add(createExpectedThirdDayRate());
        rates.add(createExpectedForthDayRate());
        roomRate.setRates(rates);
        return roomRate;
    }

    private RatePlanDTO createExpectedRatePlan() {
        RatePlanDTO ratePlan = new RatePlanDTO();
        ratePlan.setCode(RATE_PLAN_CODE);
        ratePlan.setName(RATE_PLAN_NAME);
        ratePlan.setPaymentType(PAYMENT_TYPE_POA); // TODO paymentType only cash back?
        ratePlan.setTaxes(createExpectedTaxs());
        ratePlan.setServiceCharges(createExpectedServiceCharges());
        ratePlan.setNeedGuarantee(NEED_GUARANTEE);
        ratePlan.setCancelPolicy(createExpectedCancelPolicy());
        ratePlan.setAvailGuarantees(createExpectedAvailGuarantee()); // TODO translator
        ratePlan.setFreeMeal(createExpectedFreeMeal());
        return ratePlan;
    }

    private FreeMealDTO createExpectedFreeMeal() {
        FreeMealDTO freeMeal = new FreeMealDTO();
        freeMeal.setType(FreeMealType.NONE);
        return freeMeal;
    }

    private RoomTypeDTO createExpectedRoomType() {
        RoomTypeDTO roomType = new RoomTypeDTO();
        roomType.setCode(ROOM_TYPE_CODE);
        roomType.setName(ROOM_TYPE_NAME);
        return roomType;
    }

    private RateDTO createExpectedForthDayRate() {
        return createExpectedRateDTO(NUMBER_THREE);
    }

    private RateDTO createExpectedThirdDayRate() {
        return createExpectedRateDTO(NUMBER_TWO);
    }

    private RateDTO createExpectedSecondDayRate() {
        return createExpectedRateDTO(NUMBER_ONE);
    }

    private RateDTO createExpectedFirstDayRate() {
        return createExpectedRateDTO(NUMBER_ZERO);
    }

    private RateDTO createExpectedRateDTO(int index) {
        RateDTO rate = new RateDTO();
        rate.setDateRange(createExpectedDateRangeDTO());
        rate.setPureAmount(createExpectedAmountDTO(CURRENCY, AMOUNT_BEFORE_TAXS[index]));
        rate.setMealsIncluded(new MealsIncludedDTO(MealsIncludedType.UNKNOWN));
        return rate;
    }

    private List<ChargeItemDTO> createExpectedServiceCharges() {
        List<ChargeItemDTO> serviceCharges = new ArrayList<ChargeItemDTO>();
        ChargeItemDTO serviceCharge = new ChargeItemDTO();
        serviceCharge.setUnit(ChargeUnit.PER_NIGHT);
        serviceCharge.setType(ChargeType.FIXED);
        serviceCharge.setValue(SERVICE_CHARGE_AMOUNT);
        serviceCharge.setDescription(new String(SERVICE_CHARGE_DESC + seed.incrementAndGet()));
        serviceCharges.add(serviceCharge);
        return serviceCharges;
    }

    private List<ChargeItemDTO> createExpectedTaxs() {
        List<ChargeItemDTO> taxs = new ArrayList<ChargeItemDTO>();
        ChargeItemDTO tax = new ChargeItemDTO();
        tax.setUnit(ChargeUnit.PER_NIGHT);
        tax.setValue(AMOUNT_TAX);
        tax.setType(ChargeType.FIXED);
        tax.setDescription(TAX_DESC + seed.incrementAndGet());
        taxs.add(tax);
        return taxs;
    }

    private SimpleAmountDTO createExpectedAmountDTO(Currency currency, BigDecimal amount) {
        SimpleAmountDTO simpleAmountDTO = new SimpleAmountDTO();
        simpleAmountDTO.setCurrency(currency);
        simpleAmountDTO.setAmount(amount);
        return simpleAmountDTO;
    }

    private DateRangeDTO createExpectedDateRangeDTO() {
        DateRangeDTO dateRangeDTO = new DateRangeDTO();
        dateRangeDTO.setStart(new Date());
        dateRangeDTO.setEnd(new Date());
        return dateRangeDTO;
    }

    private HotelRefDTO createExpectedHotelRef() {
        HotelRefDTO hotelRef = new HotelRefDTO();
        hotelRef.setCode(new String(HOTEL_CODE));
        hotelRef.setName(new String(HOTEL_NAME));
        return hotelRef;
    }

}
