package ai.cometandroid.network;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.internal.LinkedHashTreeMap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import ai.cometandroid.models.JsonApiResponse;
import retrofit.converter.ConversionException;
import retrofit.converter.Converter;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;
import rx.Observable;

/**
 * Created by greg on 6/26/15.
 */
public class JsonApiConverter implements Converter {

    private static String RELATIONSHIP_KEY = "relationships";
    private static String ATTRIBUTES_KEY = "attributes";

    @Override
    public Object fromBody(TypedInput body, Type type) throws ConversionException {
        try {
            InputStream in = body.in();
            String json = fromJsonApi(fromStream(in));

            if(String.class.equals(type)) return json;
            else return new Gson().fromJson(json, type);

        } catch (Exception e) {
            throw new ConversionException(e);
        }
    }

    @Override
    public TypedOutput toBody(Object object) {
        try {
            Map<String, Object> jsonApi = new LinkedHashTreeMap<>();
            jsonApi.put("data", object);
            return new JsonTypedOutput(new Gson().toJson(jsonApi).getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new AssertionError(e);
        }
    }

    private static class JsonTypedOutput implements TypedOutput {
        private final byte[] jsonBytes;

        JsonTypedOutput(byte[] jsonBytes) { this.jsonBytes = jsonBytes; }

        @Override public String fileName() { return null; }
        @Override public String mimeType() { return "application/json; charset=UTF-8"; }
        @Override public long length() { return jsonBytes.length; }
        @Override public void writeTo(OutputStream out) throws IOException { out.write(jsonBytes); }
    }

    private static String fromStream(InputStream in) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        StringBuilder out = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            out.append(line);
            out.append("\r\n");
        }
        return out.toString();
    }

    private static String fromJsonApi(String json) throws Exception {
        Gson gson = new Gson();

        JsonApiResponse response = gson.fromJson(json, JsonApiResponse.class);
        List<Map<String, Object>> data = new ArrayList<>();

        response.data()
            .doOnNext(stringObjectMap -> {
                stringObjectMap.putAll((Map<String, Object>) stringObjectMap.get(ATTRIBUTES_KEY));
                stringObjectMap.remove(ATTRIBUTES_KEY);
                Observable.from(((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).keySet())
                    .filter(key -> ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key) instanceof Map || ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key) instanceof List)
                    .subscribe(key -> {
                        stringObjectMap.put(key, ((Map<String, Object>) ((Map<String, Object>) stringObjectMap.get(RELATIONSHIP_KEY)).get(key)).get("data"));

                        Observable<Map<String, Object>> inner;
                        Object includedLinks = stringObjectMap.get(key);

                        if (includedLinks instanceof List) inner = Observable.from((List<Map<String, Object>>) includedLinks);
                        else inner = Observable.just((Map<String, Object>) includedLinks);

                        inner.forEach(link -> response.included()
                            .filter(included -> included.get("type").equals(link.get("type")) && included.get("id").equals(link.get("id")))
                            .first()
                            .subscribe(included -> link.putAll((Map<String, Object>) included.get(ATTRIBUTES_KEY))));
                    });
                stringObjectMap.remove(RELATIONSHIP_KEY);
            })
            .subscribe(datum -> data.add(datum));

        String formatted;

        if(data.size() == 1) {
            formatted =  gson.toJson(data.get(0));
        }else {
            formatted =  gson.toJson(data);
        }

        return formatted;
    }

}