/**
 * This file is part of Threema Web.
 *
 * Threema Web is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
 */

declare const angular: ng.IAngularStatic;

declare namespace threema {

    interface Avatar {
        // Low resolution avatar URI
        low?: ArrayBuffer;
        // High resolution avatar URI
        high?: ArrayBuffer;
    }

    /**
     * Messages that are sent through the secure data channel as encrypted msgpack bytes.
     */
    interface WireMessage {
        type: string;
        subType: string;
        args?: any;
        data?: any;
    }

    type MessageType = 'text' | 'image' | 'video' | 'audio' | 'location' | 'contact' |
                       'status' | 'ballot' | 'file' | 'voipStatus' | 'unknown';
    type MessageState = 'delivered' | 'read' | 'send-failed' | 'sent' | 'user-ack' |
                        'user-dec' | 'pending' | 'sending';

    const enum InitializationStep {
        ClientInfo = 'client info',
        Conversations = 'conversations',
        Receivers = 'receivers',
        Profile = 'profile',
    }

    interface InitializationStepRoutine {
        requiredSteps: InitializationStep[];
        callback: any;
    }

    interface Thumbnail {
        img?: string;
        preview: string;
        width: number;
        height: number;
    }

    const enum EventType {
        Created = 'created',
        Sent = 'sent',
        Delivered = 'delivered',
        Read = 'read',
        Acked = 'acked',
        Modified = 'modified',
    }

    /**
     * A message event, e.g. when it was delivered, read or modified.
     */
    interface MessageEvent {
        // The event type
        type: EventType;

        // Unix timestamp in seconds
        date: number;
    }

    /**
     * A Threema chat message.
     */
    interface Message {
        type: MessageType;
        id: string;
        body: string;
        thumbnail?: Thumbnail;
        date?: number;
        events?: MessageEvent[];
        sortKey: number;
        partnerId: string;
        isOutbox: boolean;
        isStatus: boolean;
        caption?: string;
        statusType?: 'text' | 'firstUnreadMessage';
        unread?: boolean;
        state?: MessageState;
        quote?: Quote;
        file?: FileInfo;
        video?: VideoInfo;
        audio?: AudioInfo;
        voip?: VoipStatusInfo;
        location?: LocationInfo;
        // only for temporary Messages
        temporaryId?: string;
        errorMessage?: string;
    }

    interface FileInfo {
        description: string;
        name: string;
        size: number;
        type: string;
        inApp: boolean;
    }

    interface VideoInfo {
        duration: number;
        size: number;
    }

    interface AudioInfo {
        duration: number;
    }

    interface VoipStatusInfo {
        status: number;
    }

    interface LocationInfo {
        lat: number;
        lon: number;
        accuracy: number;
        description: string;
        address?: string;
    }

    interface BlobInfo {
        buffer: ArrayBuffer;
        mimetype: string;
        filename: string;
    }

    /**
     * All possible receiver types.
     */
    type ReceiverType = 'me' | 'contact' | 'group' | 'distributionList';

    /**
     * Access Object for receivers
     */

    interface ReceiverAccess {
        canDelete?: boolean;
    }

    interface ContactReceiverAccess extends ReceiverAccess {
        canChangeAvatar: boolean;
        canChangeFirstName: boolean;
        canChangeLastName: boolean;
    }

    interface GroupReceiverAccess extends ReceiverAccess {
        canChangeAvatar: boolean;
        canChangeName: boolean;
        canChangeMembers?: boolean;
        canLeave?: boolean;
        canSync?: boolean;
    }

    interface DistributionListReceiverAccess extends ReceiverAccess {
        canChangeMembers?: boolean;
    }

    const enum IdentityType {
        Regular = 0,
        Work,
    }

    /**
     * The base class for a receiver. Only type and id.
     */
    interface BaseReceiver {
        id: string;
        type: ReceiverType;
    }

    /**
     * A generic receiver.
     *
     * Note that the id is not unique for all receivers, only for all receivers
     * of a certain type. The primary key for a receiver is the tuple (type, id).
     */
    interface Receiver extends BaseReceiver {
        // The display name
        displayName: string;

        // The color used for the avatar
        color: string;

        // The avatar, may be set if already fetched
        avatar?: Avatar;

        // Permissions towards this receiver
        access: ReceiverAccess;

        // Whether the chat with this receiver is locked.
        locked?: boolean;

        // Whether the chat with this receiver is visible.
        visible?: boolean;
    }

    /**
     * A contact.
     */
    interface ContactReceiver extends Receiver {
        // Flag indicating whether this is the own profile or another contact
        type: 'contact' | 'me';

        // Public nickname, if set
        publicNickname?: string;

        // First name, if set
        firstName?: string;

        // Last name, if set
        lastName?: string;

        // Verification level integer (1-3)
        verificationLevel?: number;

        // Feature mask
        featureMask: number | null;

        // The identity state
        state: 'ACTIVE' | 'INACTIVE';

        // The Threema public key
        publicKey: ArrayBuffer;

        // System confact information
        systemContact?: SystemContact;

        // Permissions towards this contact
        access: ContactReceiverAccess;

        // Whether this is a contact from the same Threema Work package.
        // Only relevant for Threema Work users.
        isWork?: boolean;

        // Whether this contact is blocked
        isBlocked?: boolean;

        // The identity type.
        // 0 - Regular Threema user.
        // 1 - Threema Work user.
        identityType?: IdentityType;
    }

    /**
     * Own contact.
     */
    interface MeReceiver extends ContactReceiver {
        type: 'me';
    }

    /**
     * A group.
     */
    interface GroupReceiver extends Receiver {
        type: 'group';
        disabled: boolean;
        members: string[];
        administrator: string;
        access: GroupReceiverAccess;
        createdAt?: string;
    }

    /**
     * A distribution list.
     */
    interface DistributionListReceiver extends Receiver {
        type: 'distributionList';
        members: string[];
        access: DistributionListReceiverAccess;
    }

    interface SystemContact {
        emails?: SystemContactEmail[];
        phoneNumbers?: SystemContactPhone[];
    }

    interface SystemContactEmail {
        label: string;
        address: string;
    }

    interface SystemContactPhone {
        label: string;
        number: string;
    }

    /**
     * A conversation.
     */
    interface Conversation {
        type: ReceiverType;
        id: string;
        position?: number;
        messageCount: number;
        unreadCount: number;
        latestMessage: Message | null;
        receiver?: Receiver;
        avatar?: ArrayBuffer;
        isMuted?: boolean;
        isStarred?: boolean;
    }

    /**
     * A conversation with a position field, used for updating a conversation.
     */
    interface ConversationWithPosition extends Conversation {
        position: number;
    }

    /**
     * Connection state in the welcome dialog.
     *
     * States:
     *
     * - new: Initial state
     * - connecting: Connecting to signaling server
     * - push: When trying to reconnect, waiting for push notification to arrive
     * - manual_start: When trying to reconnect, waiting for manual session start
     * - already_connected: When the user is already connected in another tab or window
     * - waiting: Waiting for new-responder message from signaling server
     * - peer_handshake: Doing SaltyRTC handshake with the peer
     * - loading: Loading initial data
     * - done: Initial loading is finished
     * - closed: Connection is closed
     *
     */
    type ConnectionBuildupState = 'new' | 'connecting' | 'push' | 'manual_start' | 'already_connected'
        | 'waiting' | 'peer_handshake' | 'loading' | 'done' | 'closed';

    interface ConnectionBuildupStateChange {
        state: ConnectionBuildupState;
        prevState: ConnectionBuildupState;
    }

    /**
     * Connection state of the task peer connection.
     */
    const enum TaskConnectionState {
        New = 'new',
        Connecting = 'connecting',
        Connected = 'connected',
        Reconnecting = 'reconnecting',
        Disconnected = 'disconnected',
    }

    /**
     * Connection state of the WebRTC peer connection.
     */
    const enum GlobalConnectionState {
        Ok = 'ok',
        Warning = 'warning',
        Error = 'error',
    }

    /**
     * Type of message to be sent to a receiver.
     */
    type MessageContentType = 'text' | 'file';

    interface MessageData {
        // optional quote object
        quote?: Quote;
    }

    /**
     * Payload for a file message.
     */
    interface FileMessageData extends MessageData {
        // File name
        name: string;
        // File MIME type
        fileType: string;
        // Size in bytes
        size: number;
        // File bytes
        data: ArrayBuffer;
        // Caption string
        caption?: string;
        // Send as file message
        sendAsFile?: boolean;
    }

    /**
     * Payload for a text message.
     */
    interface TextMessageData extends MessageData {
        // Text to be sent
        text: string;
    }

    /**
     * The $stateParams format used for the welcome controller.
     */
    interface WelcomeStateParams extends ng.ui.IStateParamsService {
        initParams: null | {keyStore: saltyrtc.KeyStore, peerTrustedKey: Uint8Array};
    }

    interface CreateReceiverStateParams extends ng.ui.IStateParamsService {
        type: ReceiverType;
        initParams: null | {identity: string | null};
    }

    interface ConversationStateParams extends ng.ui.IStateParamsService {
        type: ReceiverType;
        id: string;
        initParams: null | {text: string | null};
    }

    interface Quote {
        identity: string;
        text: string;
    }

    const enum PushTokenType {
        Gcm = 'gcm',
        Apns = 'apns',
    }

    const enum PushTokenPrefix {
        Gcm = 'g',
        Apns = 'a',
    }

    const enum WakeupType {
        // A full reconnect (by entering the password on the main screen).
        FullReconnect = '0',
        // A wakeup, as implemented by the iOS app.
        Wakeup = '1',
    }

    interface TrustedKeyStoreData {
        ownPublicKey: Uint8Array;
        ownSecretKey: Uint8Array;
        peerPublicKey: Uint8Array;
        pushToken: string | null;
        pushTokenType: PushTokenType | null;
    }

    const enum BrowserName {
        Chrome = 'chrome',
        Firefox = 'firefox',
        InternetExplorer = 'ie',
        Edge = 'edge',
        Opera = 'opera',
        Safari = 'safari',
    }

    interface BrowserInfo {
        chrome: boolean;
        firefox: boolean;
        ie: boolean;
        edge: boolean;
        opera: boolean;
        safari: boolean;
        name?: BrowserName;
        version?: number;
        textInfo?: string;
    }

    interface PromiseCallbacks {
        resolve: (arg: any) => void;
        reject: (arg: any) => void;
    }

    interface PromiseRequestResult<T> {
        success: boolean;
        error?: string;
        data?: T;
    }

    type OnRemovedCallback = (identity: string) => void;

    const enum ControllerModelMode {
        NEW = 'new',
        VIEW = 'view',
        EDIT = 'edit',
        CHAT = 'chat',
    }

    const enum ContactReceiverFeature {
        AUDIO = 0x01,
        GROUP_CHAT = 0x02,
        BALLOT = 0x04,
        FILE = 0x08,
        VOIP = 0x10,
    }

    interface ControllerModel<T extends BaseReceiver> {
        /**
         * The title shown in the header.
         */
        subject: string;

        /**
         * Loading state.
         */
        isLoading: boolean;

        /**
         * Save the changes, return a promise with the receiver.
         */
        save(): Promise<T>;

        /**
         * Delete all messages in this conversation.
         */
        clean(ev: any): any;

        /**
         * Validate this receiver.
         */
        isValid(): boolean;

        /*
         * Return whether this receiver can be chatted with.
         */
        canChat(): boolean;

        /**
         * Can this receiver be edited?
         */
        canEdit(): boolean;

        /**
         * Can this receiver be cleaned?
         */
        canClean(): boolean;

        /*
         * Return whether this receiver can show a QR code of the public key.
         */
        canShowQr(): boolean;

        /**
         * The editing mode, e.g. view or edit this receiver.
         */
        getMode(): ControllerModelMode;

        /**
         * Set the on removed callback.
         */
        setOnRemoved(callback: OnRemovedCallback): void;

        /**
         * Callback called when the members change.
         */
        onChangeMembers(identities: string[]): void;

        /**
         * Return the members of this receiver.
         */
        getMembers(): string[];
    }

    interface Alert {
        source: string;
        type: string;
        message: string;
    }

    interface ReceiverListener {
        onRemoved(receiver: Receiver);
    }

    interface Config {
        SELF_HOSTED: boolean;
        PREV_PROTOCOL_LAST_VERSION: string | null;
        SALTYRTC_PORT: number;
        SALTYRTC_SERVER_KEY: string | null;
        SALTYRTC_HOST: string | null;
        SALTYRTC_HOST_PREFIX: string | null;
        SALTYRTC_HOST_SUFFIX: string | null;
        ICE_SERVERS: RTCIceServer[];
        PUSH_URL: string;
        DEBUG: boolean;
        MSG_DEBUGGING: boolean;
        MSGPACK_DEBUGGING: boolean;
        ICE_DEBUGGING: boolean;
    }

    interface InitialConversationData {
        draft: string;
        initialText: string;
    }

    interface BrowserMinVersions {
        FF: number;
        CHROME: number;
        OPERA: number;
        SAFARI: number;
    }

    interface BatteryStatus {
        percent: number;
        isCharging: boolean;
    }

    const enum OperatingSystem {
        Android = 'android',
        Ios = 'ios',
    }

    interface ClientInfo {
        device: string;
        os: OperatingSystem;
        osVersion: string;
        isWork: boolean;
        pushToken?: string;
        configuration: AppConfig;
        capabilities: AppCapabilities;
    }

    interface AppConfig {
        showLocationPreview: boolean;
        voipEnabled: boolean;
        voipForceTurn: boolean;
        largeSingleEmoji: boolean;
        showInactiveIds: boolean;
    }

    interface AppCapabilities {
        maxGroupSize: number;
        distributionLists: boolean;
        mdm?: MdmRestrictions;
    }

    interface MdmRestrictions {
        disableAddContact?: boolean;
        disableCreateGroup?: boolean;
        disableSaveToGallery?: boolean;
        disableExport?: boolean;
        disableMessagePreview?: boolean;
        disableCalls?: boolean;
        readonlyProfile?: boolean;
    }

    interface Mention {
        identity: string;
        query: string;
        isAll: boolean;
    }

    interface WordResult {
        // The trimmed word
        word: string;
        // The length of the untrimmed word
        realLength: number;
    }

    const enum ChosenTask {
        None,
        WebRTC,
        RelayedData,
    }

    namespace Container {
        interface ReceiverData {
            contacts: ContactReceiver[];
            groups: GroupReceiver[];
            distributionLists: DistributionListReceiver[];
        }

        interface Converter {
            unicodeToEmoji(message);
            addReceiverToConversation(receivers: Receivers);
        }
        interface Filters {
            hasData(receivers);
            hasContact(contacts);
            isValidMessage(contacts);
        }
        interface Receivers {
            me: MeReceiver;
            contacts: Map<string, ContactReceiver>;
            groups: Map<string, GroupReceiver>;
            distributionLists: Map<string, DistributionListReceiver>;
            get(receiverType: ReceiverType): Receiver | Map<string, Receiver>;
            getData(receiver: BaseReceiver): Receiver | null;
            set(data: ReceiverData): void;
            setMe(data: MeReceiver): void;
            setContacts(data: ContactReceiver[]): void;
            setGroups(data: GroupReceiver[]): void;
            setDistributionLists(data: DistributionListReceiver[]): void;
            extend(receiverType: ReceiverType, data: Receiver): Receiver;
            extendDistributionList(data: DistributionListReceiver): DistributionListReceiver;
            extendGroup(data: GroupReceiver): GroupReceiver;
            extendMe(data: MeReceiver): MeReceiver;
            extendContact(data: ContactReceiver): ContactReceiver;
        }

        interface Conversations {
            get(): Conversation[];
            set(data: Conversation[]): void;
            find(pattern: Conversation | Receiver): Conversation | null;
            add(conversation: Conversation): void;
            updateOrAdd(conversation: Conversation): Conversation | null;
            remove(conversation: Conversation): void;
            setFilter(filter: (data: Conversation[]) => Conversation[]): void;
            setConverter(converter: (data: Conversation) => Conversation): void;
        }

        interface Messages {
            converter: (data: Message) => Message;
            getList(receiver: Receiver): Message[];
            clear($scope: ng.IScope): void;
            clearReceiverMessages(receiver: Receiver): number;
            contains(receiver: Receiver): boolean;
            hasMore(receiver: Receiver): boolean;
            setMore(receiver: Receiver, more: boolean): void;
            getReferenceMsgId(receiver: Receiver): string;
            isRequested(receiver: Receiver): boolean;
            setRequested(receiver: Receiver): void;
            clearRequested(receiver): void;
            addNewer(receiver: Receiver, messages: Message[]): void;
            addOlder(receiver: Receiver, messages: Message[]): void;
            update(receiver: Receiver, message: Message): boolean;
            setThumbnail(receiver: Receiver, messageId: string, thumbnailImage: string): boolean;
            remove(receiver: Receiver, messageId: string): boolean;
            removeTemporary(receiver: Receiver, temporaryMessageId: string): boolean;
            bindTemporaryToMessageId(receiver: Receiver, temporaryId: string, messageId: string): boolean;
            notify(receiver: Receiver, $scope: ng.IScope): void;
            register(receiver: Receiver, $scope: ng.IScope, callback: any): Message[];
            updateFirstUnreadMessage(receiver: Receiver);
        }

        interface Typing {
            setTyping(receiver: ContactReceiver): void;
            unsetTyping(receiver: ContactReceiver): void;
            isTyping(receiver: ContactReceiver): boolean;
        }

        interface Drafts {
            setQuote(receiver: Receiver, quote: Quote): void;
            removeQuote(receiver: Receiver): void;
            getQuote(receiver: Receiver): Quote;
            setText(receiver: Receiver, draftMessage: string): void;
            removeText(receiver: Receiver): void;
            getText(receiver: Receiver): string;
        }

        interface Factory {
            Converter: Container.Converter;
            Filters: Container.Filters;
            createReceivers: () => Receivers;
            createConversations: () => Conversations;
            createMessages: () => Messages;
            createTyping: () => Typing;
            createDrafts: () => Drafts;
        }
    }
}
