#!/usr/bin/env python

'''
This program detects the QR-codes using OpenCV Library.

Usage:
   qrcode.py
'''


# Python 2/3 compatibility
from __future__ import print_function

import numpy as np
import cv2 as cv

import argparse
import sys

PY3 = sys.version_info[0] == 3
if PY3:
    xrange = range

# Colors for distinguishing multiple QR codes visually
QR_COLORS = [
    (0, 255, 0),    # green
    (255, 0, 0),    # blue
    (0, 0, 255),    # red
    (255, 255, 0),  # cyan
    (0, 255, 255),  # yellow
    (255, 0, 255),  # magenta
    (128, 255, 0),  # lime
    (255, 128, 0),  # orange
]


class QrSample:
    def __init__(self, args):
        self.fname = ''
        self.fext = ''
        self.fsaveid = 0
        self.input = args.input
        self.detect = args.detect
        self.out = args.out
        self.multi = args.multi
        self.saveDetections = args.save_detections
        self.saveAll = args.save_all
        self.arucoBased = args.aruco_based

    def getQRModeString(self):
        msg1 = "multi " if self.multi else ""
        msg2 = "detector" if self.detect else "decoder"
        msg = "QR {:s}{:s}".format(msg1, msg2)
        return msg

    def drawFPS(self, result, fps):
        message = '{:.2f} FPS({:s})'.format(fps, self.getQRModeString())
        cv.putText(result, message, (20, 20), 1,
                   cv.FONT_HERSHEY_DUPLEX, (0, 0, 255))

    def drawQRCodeContours(self, image, cnt, color=(0, 255, 0)):
        if cnt.size != 0:
            rows, cols, _ = image.shape
            show_radius = 2.813 * ((rows / cols) if rows > cols else (cols / rows))
            contour_radius = show_radius * 0.4
            cv.drawContours(image, [cnt], 0, color, int(round(contour_radius)))
            tpl = cnt.reshape((-1, 2))
            for x in tuple(tpl.tolist()):
                cv.circle(image, tuple(x), int(round(contour_radius)), color, -1)

    def drawQRCodeResults(self, result, points, decode_info, fps):
        n = len(points)
        if isinstance(decode_info, str):
            decode_info = [decode_info]
        if n > 0:
            for i in range(n):
                cnt = np.array(points[i]).reshape((-1, 1, 2)).astype(np.int32)
                color = QR_COLORS[i % len(QR_COLORS)]
                self.drawQRCodeContours(result, cnt, color)
                msg = 'QR[{:d}]@{} : '.format(i, *(cnt.reshape(1, -1).tolist()))
                print(msg, end="")
                if len(decode_info) > i:
                    if decode_info[i]:
                        print("'", decode_info[i], "'")
                    else:
                        print("Can't decode QR code")
                else:
                    print("Decode information is not available (disabled)")
        else:
            print("QRCode not detected!")
        self.drawFPS(result, fps)

    def runQR(self, qrCode, inputimg):
        if not self.multi:
            if not self.detect:
                decode_info, points, _ = qrCode.detectAndDecode(inputimg)
                dec_info = decode_info
            else:
                _, points = qrCode.detect(inputimg)
                dec_info = []
        else:
            if not self.detect:
                _, decode_info, points, _ = qrCode.detectAndDecodeMulti(
                    inputimg)
                dec_info = decode_info
            else:
                _, points = qrCode.detectMulti(inputimg)
                dec_info = []
        if points is None:
            points = []
        return points, dec_info

    def DetectQRFrmImage(self, inputfile):
        inputimg = cv.imread(inputfile, cv.IMREAD_COLOR)
        if inputimg is None:
            print('ERROR: Can not read image: {}'.format(inputfile))
            return
        print('Run {:s} on image [{:d}x{:d}]'.format(
            self.getQRModeString(), inputimg.shape[1], inputimg.shape[0]))

        if self.arucoBased:
            qrCode = cv.QRCodeDetectorAruco()
        else:
            qrCode = cv.QRCodeDetector()

        count = 10
        timer = cv.TickMeter()
        for _ in range(count):
            timer.start()
            points, decode_info = self.runQR(qrCode, inputimg)
            timer.stop()
        fps = count / timer.getTimeSec()
        print('FPS: {}'.format(fps))
        result = inputimg
        self.drawQRCodeResults(result, points, decode_info, fps)
        cv.imshow("QR", result)
        cv.waitKey(1)
        if self.out != '':
            outfile = self.fname + self.fext
            print("Saving Result: {}".format(outfile))
            cv.imwrite(outfile, result)

        print("Press any key to exit ...")
        cv.waitKey(0)
        print("Exit")

    def processQRCodeDetection(self, qrcode, frame):
        if len(frame.shape) == 2:
            result = cv.cvtColor(frame, cv.COLOR_GRAY2BGR)
        else:
            result = frame
        print('Run {:s} on video frame [{:d}x{:d}]'.format(
            self.getQRModeString(), frame.shape[1], frame.shape[0]))
        timer = cv.TickMeter()
        timer.start()
        points, decode_info = self.runQR(qrcode, frame)
        timer.stop()

        fps = 1 / timer.getTimeSec()
        self.drawQRCodeResults(result, points, decode_info, fps)
        return fps, result, points

    def DetectQRFrmCamera(self):
        cap = cv.VideoCapture(0)
        if not cap.isOpened():
            print("Cannot open the camera")
            return
        print("Press 'm' to switch between detectAndDecode and detectAndDecodeMulti")
        print("Press 'd' to switch between decoder and detector")
        print("Press ' ' (space) to save result into images")
        print("Press 'ESC' to exit")

        if self.arucoBased:
            qrcode = cv.QRCodeDetectorAruco()
        else:
            qrcode = cv.QRCodeDetector()

        while True:
            ret, frame = cap.read()
            if not ret:
                print("End of video stream")
                break
            forcesave = self.saveAll
            result = frame
            try:
                fps, result, corners = self.processQRCodeDetection(qrcode, frame)
                print('FPS: {:.2f}'.format(fps))
                forcesave |= self.saveDetections and (len(corners) != 0)
            except cv.error as e:
                print("Error exception: ", e)
                forcesave = True
            cv.imshow("QR code", result)
            code = cv.waitKey(1)
            if code < 0 and (not forcesave):
                continue
            if code == ord(' ') or forcesave:
                fsuffix = '-{:05d}'.format(self.fsaveid)
                self.fsaveid += 1
                fname_in = self.fname + fsuffix + "_input.png"
                print("Saving QR code detection result: '{}' ...".format(fname_in))
                cv.imwrite(fname_in, frame)
                print("Saved")
            if code == ord('m'):
                self.multi = not self.multi
                msg = 'Switching QR code mode ==> {:s}'.format(
                    "detectAndDecodeMulti" if self.multi else "detectAndDecode")
                print(msg)
            if code == ord('d'):
                self.detect = not self.detect
                msg = 'Switching QR code mode ==> {:s}'.format(
                    "detect" if self.detect else "decode")
                print(msg)
            if code == 27:
                print("'ESC' is pressed. Exiting...")
                break
        print("Exit.")


def main():
    parser = argparse.ArgumentParser(
        description='This program detects the QR-codes input images using OpenCV Library.')
    parser.add_argument(
        '-i',
        '--input',
        help="input image path (for example, 'opencv_extra/testdata/cv/qrcode/multiple/*_qrcodes.png)",
        default="",
        metavar="")
    parser.add_argument(
        '--aruco_based',
        help="use aruco-based detector",
        action='store_true')
    parser.add_argument(
        '-d',
        '--detect',
        help="detect QR code only (skip decoding) (default: False)",
        action='store_true')
    parser.add_argument(
        '-m',
        '--multi',
        help="enable multiple qr-codes detection",
        action='store_true')
    parser.add_argument(
        '-o',
        '--out',
        help="path to result file (default: qr_code.png)",
        default="qr_code.png",
        metavar="")
    parser.add_argument(
        '--save_detections',
        help="save all QR detections (video mode only)",
        action='store_true')
    parser.add_argument(
        '--save_all',
        help="save all processed frames (video mode only)",
        action='store_true')
    args = parser.parse_args()
    qrinst = QrSample(args)
    if args.out != '':
        index = args.out.rfind('.')
        if index != -1:
            qrinst.fname = args.out[:index]
            qrinst.fext = args.out[index:]
        else:
            qrinst.fname = args.out
            qrinst.fext = ".png"
    if args.input != '':
        qrinst.DetectQRFrmImage(args.input)
    else:
        qrinst.DetectQRFrmCamera()


if __name__ == '__main__':
    print(__doc__)
    main()
    cv.destroyAllWindows()
