/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.android.bips;

import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.util.Log;
import android.widget.Toast;

import com.android.bips.discovery.ConnectionListener;
import com.android.bips.discovery.DiscoveredPrinter;
import com.android.bips.ipp.CapabilitiesCache;
import com.android.bips.jni.LocalPrinterCapabilities;
import com.android.bips.p2p.P2pPrinterConnection;
import com.android.bips.p2p.P2pUtils;
import com.android.bips.ui.MoreOptionsActivity;

import java.net.InetAddress;
import java.util.Collections;
import java.util.UUID;

/**
 * A session-specific printer record. Encapsulates logic for getting the latest printer
 * capabilities as necessary.
 */
class LocalPrinter implements CapabilitiesCache.OnLocalPrinterCapabilities {
    private static final String TAG = LocalPrinter.class.getSimpleName();
    private static final boolean DEBUG = false;

    private final BuiltInPrintService mPrintService;
    private final LocalDiscoverySession mSession;
    private final PrinterId mPrinterId;
    private long mLastSeenTime = System.currentTimeMillis();
    private boolean mFound = true;
    private boolean mTracking = false;
    private LocalPrinterCapabilities mCapabilities;
    private DiscoveredPrinter mDiscoveredPrinter;
    private P2pPrinterConnection mTrackingConnection;

    LocalPrinter(BuiltInPrintService printService, LocalDiscoverySession session,
            DiscoveredPrinter discoveredPrinter) {
        mPrintService = printService;
        mSession = session;
        mDiscoveredPrinter = discoveredPrinter;
        mPrinterId = discoveredPrinter.getId(printService);
    }

    /** Return the address of the printer or {@code null} if not known */
    public InetAddress getAddress() {
        if (mCapabilities != null) {
            return mCapabilities.inetAddress;
        }
        return null;
    }

    /** Return true if this printer should be aged out */
    boolean isExpired() {
        return !mFound && (System.currentTimeMillis() - mLastSeenTime)
                > LocalDiscoverySession.PRINTER_EXPIRATION_MILLIS;
    }

    /** Return capabilities or null if not present */
    LocalPrinterCapabilities getCapabilities() {
        return mCapabilities;
    }

    /** Create a PrinterInfo from this record or null if not possible */
    PrinterInfo createPrinterInfo(boolean knownGood) {
        if (mCapabilities == null) {
            if (P2pUtils.isP2p(mDiscoveredPrinter)) {
                // Allow user to select a P2P to establish a connection
                PrinterInfo.Builder builder = new PrinterInfo.Builder(
                        mPrinterId, mDiscoveredPrinter.name,
                        PrinterInfo.STATUS_IDLE)
                        .setIconResourceId(R.drawable.ic_printer)
                        .setDescription(mPrintService.getDescription(mDiscoveredPrinter))
                        .setInfoIntent(getMoreOptionsActivityPendingIntent());
                return builder.build();
            } else if (!knownGood) {
                // Ignore unknown LAN printers with no caps
                return null;
            }
        } else if (!mCapabilities.isSupported) {
            // Fail out if capabilities indicate not-supported.
            return null;
        }

        // Get the most recently discovered version of this printer
        DiscoveredPrinter printer = mPrintService.getDiscovery()
                .getPrinter(mDiscoveredPrinter.getUri());
        if (printer == null) {
            return null;
        }

        boolean idle = mFound && mCapabilities != null;
        PrinterInfo.Builder builder = new PrinterInfo.Builder(
                mPrinterId, printer.name,
                idle ? PrinterInfo.STATUS_IDLE : PrinterInfo.STATUS_UNAVAILABLE)
                .setIconResourceId(R.drawable.ic_printer)
                .setDescription(mPrintService.getDescription(mDiscoveredPrinter))
                .setInfoIntent(getMoreOptionsActivityPendingIntent());

        if (mCapabilities != null) {
            // Add capabilities if we have them
            PrinterCapabilitiesInfo.Builder capabilitiesBuilder =
                    new PrinterCapabilitiesInfo.Builder(mPrinterId);
            mCapabilities.buildCapabilities(mPrintService, capabilitiesBuilder);
            builder.setCapabilities(capabilitiesBuilder.build());
        }

        return builder.build();
    }

    @Override
    public void onCapabilities(LocalPrinterCapabilities capabilities) {
        if (mSession.isDestroyed() || !mSession.isKnown(mPrinterId)) {
            return;
        }

        if (capabilities == null) {
            if (DEBUG) Log.d(TAG, "No capabilities so removing printer " + this);
            mSession.removePrinters(Collections.singletonList(mPrinterId));
        } else {
            mCapabilities = capabilities;
            mSession.handlePrinter(this);
        }
    }

    PrinterId getPrinterId() {
        return mPrinterId;
    }

    /** Return true if the printer is in a "found" state according to discoveries */
    boolean isFound() {
        return mFound;
    }

    /**
     * Indicate the printer was found and gather capabilities if we don't have them
     */
    void found(DiscoveredPrinter printer) {
        mDiscoveredPrinter = printer;
        mLastSeenTime = System.currentTimeMillis();
        mFound = true;

        // Check for cached capabilities
        LocalPrinterCapabilities capabilities = mPrintService.getCapabilitiesCache()
                .get(mDiscoveredPrinter);

        if (capabilities != null) {
            // Report current capabilities
            onCapabilities(capabilities);
        } else {
            // Announce printer and fetch capabilities immediately if possible
            mSession.handlePrinter(this);
            if (!P2pUtils.isP2p(mDiscoveredPrinter)) {
                mPrintService.getCapabilitiesCache().request(mDiscoveredPrinter,
                        mSession.isPriority(mPrinterId), this);
            } else if (mTracking) {
                startTracking();
            }
        }
    }

    /**
     * Begin tracking (getting latest capabilities) for this printer
     */
    public void track() {
        if (DEBUG) Log.d(TAG, "track " + mDiscoveredPrinter);
        startTracking();
    }

    private void startTracking() {
        mTracking = true;
        if (mTrackingConnection != null) {
            return;
        }

        // For any P2P printer, obtain a connection
        if (P2pUtils.isP2p(mDiscoveredPrinter)
                || P2pUtils.isOnConnectedInterface(mPrintService, mDiscoveredPrinter)) {
            ConnectionListener listener = new ConnectionListener() {
                @Override
                public void onConnectionComplete(DiscoveredPrinter printer) {
                    if (DEBUG) Log.d(TAG, "connection complete " + printer);
                    if (printer == null) {
                        mTrackingConnection = null;
                    }
                }

                @Override
                public void onConnectionDelayed(boolean delayed) {
                    if (DEBUG) Log.d(TAG, "connection delayed=" + delayed);
                    if (delayed) {
                        Toast.makeText(mPrintService, R.string.connect_hint_text,
                                Toast.LENGTH_LONG).show();
                    }
                }
            };
            mTrackingConnection = new P2pPrinterConnection(mPrintService,
                    mDiscoveredPrinter, listener);
        }
    }

    /**
     * Stop tracking this printer
     */
    void stopTracking() {
        if (mTrackingConnection != null) {
            mTrackingConnection.close();
            mTrackingConnection = null;
        }
        mTracking = false;
    }

    /**
     * Mark this printer as not found (will eventually expire)
     */
    void notFound() {
        mFound = false;
        mLastSeenTime = System.currentTimeMillis();
    }

    /** Return the UUID for this printer if it is known */
    public Uri getUuid() {
        return mDiscoveredPrinter.uuid;
    }

    @Override
    public String toString() {
        return mDiscoveredPrinter.toString();
    }

    /**
     * Returns a pending intent to the more options activity with the given printer info as an extra
     * @return Pending Intent
     */
    public PendingIntent getMoreOptionsActivityPendingIntent() {
        return PendingIntent.getActivity(
                mPrintService,
                mPrinterId.hashCode(),
                new Intent(mPrintService, MoreOptionsActivity.class)
                        .setIdentifier(UUID.randomUUID().toString())
                        .putExtra(MoreOptionsActivity.EXTRA_PRINTER_ID, mPrinterId),
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );
    }
}
