/*
 * Copyright 2016 Sam Sun <me@samczsun.com>
 *
 *    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 com.samczsun.skype4j.internal.participants.info;

import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.samczsun.skype4j.Skype;
import com.samczsun.skype4j.chat.Chat;
import com.samczsun.skype4j.exceptions.ChatNotFoundException;
import com.samczsun.skype4j.exceptions.ConnectionException;
import com.samczsun.skype4j.exceptions.NoSuchContactException;
import com.samczsun.skype4j.internal.Endpoints;
import com.samczsun.skype4j.internal.SkypeImpl;
import com.samczsun.skype4j.internal.Utils;
import com.samczsun.skype4j.internal.client.FullClient;
import com.samczsun.skype4j.internal.utils.Encoder;
import com.samczsun.skype4j.participants.info.Contact;
import org.jsoup.helper.Validate;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.UUID;
import java.util.regex.Pattern;

public class ContactImpl implements Contact {
    private static final Pattern PHONE_NUMBER = Pattern.compile("\\+[0-9]+");

    public static Contact createContact(SkypeImpl skype, String username) throws ConnectionException {
        Validate.notEmpty(username, "Username must not be empty");
        return new ContactImpl(skype, username, getObject(skype, username));
    }

    public static Contact createContact(SkypeImpl skype, String username, JsonObject unaddedData) throws ConnectionException {
        Validate.notEmpty(username, "Username must not be empty");
        return new ContactImpl(skype, username, unaddedData);
    }

    private static JsonObject getObject(SkypeImpl skype, String username) throws ConnectionException {
        JsonArray array = Endpoints.PROFILE_INFO
                .open(skype)
                .expect(200, "While getting contact info")
                .as(JsonArray.class)
                .post(new JsonObject()
                        .add("usernames", new JsonArray()
                                .add(username)
                        )
                );
        return array.get(0).asObject();
    }

    private SkypeImpl skype;
    private String username;
    private String displayName;
    private String firstName;
    private String lastName;
    private String birthday;
    private String gender;
    private String language;
    private String avatarURL;
    private BufferedImage avatar;
    private String mood;
    private String richMood;
    private String country;
    private String city;
    private boolean isPhone;

    private boolean isAuthorized;
    private boolean isBlocked;

    // What is this?
    private String authCertificate;
    private UUID personId;
    private String type;

    public ContactImpl(SkypeImpl skype, String username, JsonObject unaddedData) throws ConnectionException {
        this.skype = skype;
        this.username = username;
        if (!PHONE_NUMBER.matcher(username).matches()) {
            updateProfile(unaddedData);
            updateContactInfo();
        } else {
            this.isPhone = true;
        }
    }

    public ContactImpl(SkypeImpl skype, JsonObject contact) throws ConnectionException {
        this.skype = skype;
        update(contact);
        updateProfile(getObject(skype, getUsername()));
    }

    private void updateContactInfo() throws ConnectionException {
        if (this.skype instanceof FullClient) {
            JsonObject obj = Endpoints.GET_CONTACT_BY_ID
                    .open(skype, skype.getUsername(), username)
                    .as(JsonObject.class)
                    .expect(200, "While getting contact info")
                    .get();
            if (obj.get("contacts").asArray().size() > 0) {
                JsonObject contact = obj.get("contacts").asArray().get(0).asObject();
                update(contact);
            } else {
                this.isAuthorized = false;
                this.isBlocked = false;
            }
        }
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public String getDisplayName() {
        return this.displayName;
    }

    @Override
    public String getFirstName() {
        return this.firstName;
    }

    @Override
    public String getLastName() {
        return this.lastName;
    }

    @Override
    public BufferedImage getAvatarPicture() throws ConnectionException {
        if (this.avatarURL != null) {
            if (this.avatar == null) {
                //Casting might not be safe.
                this.avatar = Endpoints
                        .custom(this.avatarURL, skype)
                        .expect(200, "While fetching avatar")
                        .as(BufferedImage.class)
                        .get();

            }
            BufferedImage clone = new BufferedImage(avatar.getWidth(), avatar.getHeight(), avatar.getType());
            Graphics2D g2d = clone.createGraphics();
            g2d.drawImage(avatar, 0, 0, null);
            g2d.dispose();
            return clone;
        }
        return null;
    }

    @Override
    public String getAvatarURL() {
        return this.avatarURL;
    }

    @Override
    public String getMood() {
        return this.mood;
    }

    @Override
    public String getRichMood() {
        return this.richMood;
    }

    @Override
    public String getCountry() {
        return this.country;
    }

    @Override
    public String getCity() {
        return this.city;
    }

    @Override
    public boolean isAuthorized() {
        return this.isAuthorized;
    }

    @Override
    public void authorize() throws ConnectionException {
        Endpoints.AUTHORIZE_CONTACT.open(skype, this.username).expect(200, "While authorizing contact").put();
        updateContactInfo();
    }

    @Override
    public void unauthorize() throws ConnectionException {
        if (isAuthorized) {
            Endpoints.UNAUTHORIZE_CONTACT_SELF
                    .open(skype, this.username)
                    .expect(200, "While unauthorizing contact")
                    .put();
        } else {
            Endpoints.DECLINE_CONTACT_REQUEST
                    .open(skype, this.username)
                    .expect(201, "While unauthorizing contact")
                    .put();
        }
        updateContactInfo();
    }

    @Override
    public void sendRequest(String message) throws ConnectionException, NoSuchContactException {
        Endpoints.AUTHORIZATION_REQUEST
                .open(skype, this.username)
                .on(404, (connection) -> {
                    throw new NoSuchContactException();
                })
                .expect(201, "While sending request")
                .expect(200, "While sending request")
                .put("greeting=" + Encoder.encode(message));
        updateContactInfo();
    }

    @Override
    public boolean isBlocked() {
        return this.isBlocked;
    }

    @Override
    public void block(boolean reportAbuse) throws ConnectionException {
        Endpoints.BLOCK_CONTACT
                .open(skype, this.username)
                .expect(201, "While unblocking contact")
                .put("reporterIp=127.0.0.1&uiVersion=" + Skype.VERSION + (reportAbuse ? "&reportAbuse=1" : ""));
        updateContactInfo();
    }

    @Override
    public void unblock() throws ConnectionException {
        Endpoints.UNBLOCK_CONTACT.open(skype, this.username).expect(201, "While unblocking contact").put();
        updateContactInfo();
    }

    @Override
    public boolean isPhone() {
        return this.isPhone;
    }

    @Override
    public Chat getPrivateConversation() throws ConnectionException, ChatNotFoundException {
        return skype.getOrLoadChat("8:" + this.username);
    }

    public void update(JsonObject contact) {
        this.username = contact.get("id").asString();
        this.isAuthorized = contact.get("authorized").asBoolean();
        this.isBlocked = contact.get("blocked").asBoolean();
        this.displayName = Utils.getString(contact, "display_name");
        this.avatarURL = Utils.getString(contact, "avatar_url");
        this.mood = Utils.getString(contact, "mood");
        this.type = Utils.getString(contact, "type");
        this.authCertificate = Utils.getString(contact, "auth_certificate");
        this.firstName = contact.get("name") == null ? null : Utils.getString(contact.get("name").asObject(), "first");
        if (contact.get("locations") != null) {
            JsonObject locations = contact.get("locations").asArray().get(0).asObject();
            this.country = locations.get("country") == null ? null : locations.get("country").asString();
            this.city = locations.get("city") == null ? null : locations.get("city").asString();
        }
    }

    public void updateProfile(JsonObject profile) {
        this.firstName = Utils.getString(profile, "firstname");
        this.lastName = Utils.getString(profile, "lastname");
        this.birthday = Utils.getString(profile, "birthday");

        if (profile.get("gender") != null) {
            if (profile.get("gender").isNumber()) {
                this.gender = String.valueOf(profile.get("gender").asInt());
            } else if (profile.get("gender").isString()) {
                this.gender = profile.get("gender").asString();
            } else if (profile.get("gender").isBoolean()) {
                this.gender = profile.get("gender").asBoolean() ? "1" : "0";
            } else {
                this.gender = profile.get("gender").toString();
            }
        }

        this.language = Utils.getString(profile, "language");
        this.avatarURL = Utils.getString(profile, "avatarUrl");

        if (this.displayName == null)
            this.displayName = Utils.getString(profile, "displayname");
        if (this.mood == null)
            this.mood = Utils.getString(profile, "mood");
        if (this.richMood == null)
            this.richMood = Utils.getString(profile, "richMood");
        if (this.country == null)
            this.country = Utils.getString(profile, "country");
        if (this.city == null)
            this.city = Utils.getString(profile, "city");

        if (this.displayName == null) {
            if (this.firstName != null) {
                this.displayName = this.firstName;
                if (this.lastName != null) {
                    this.displayName = this.displayName + " " + this.lastName;
                }
            } else if (this.lastName != null) {
                this.displayName = this.lastName;
            } else {
                this.displayName = this.username;
            }
        }
    }
}
