#!/usr/bin/env python
#
# BSD 3-Clause License
#
# Copyright (c) 2023, Fred W6BSD
# All rights reserved.
#

import logging
import os
import re
import smtplib
import sqlite3
import ssl
import string
import sys
import yaml

from argparse import ArgumentParser, FileType
from datetime import datetime
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate
from shutil import move
from tempfile import NamedTemporaryFile

import adif_io

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from importlib.resources import files

import qrzlib

__version__ = "0.1.5"

TEXT_COLOR = (0, 0, 77)
NEW_WIDTH = 1024

CONFIG_FILENAME = "eqsl.yaml"
CONFIG_LOCATIONS = ['/etc', '~/.local', '.']

FONTS = {
  'font_call': 'Ubuntu Mono derivative Powerline Bold.ttf',
  'font_text': 'DroidSansMono.ttf',
  'font_foot': 'VeraMono-Italic.ttf',
}

logging.basicConfig(level=logging.INFO)

def draw_rectangle(draw, coord, color=(0x44, 0x79, 0x9), width=1, fill=(0x75, 0xDB, 0xCD, 190)):
  draw.rectangle(coord, outline=color, fill=fill)
  draw.rectangle(coord, outline=color, width=width)


def send_mail(qso, image):
  server = "127.0.0.1"
  qso = type('QSO', (object,), qso)
  mail_template = config.mail_template + '\n' * 3
  template = string.Template(mail_template).safe_substitute

  if not qso.email:
    logging.error('No email provided for %s', qso.CALL)
    return

  msg = MIMEMultipart()
  msg['From'] = config.smtp_from
  msg['To'] = qso.email
  msg['Date'] = formatdate(localtime=True)
  msg['Subject'] = f"Digital QSL from {qso.OPERATOR} to {qso.CALL}"

  data = {}
  data['fname'] = qso.fname
  data['qso_date'] = datetime.fromtimestamp(qso.timestamp).strftime("%A %B %d, %Y at %X UTC")
  data['freq_rx'] = qso.FREQ_RX
  data['mode'] = qso.MODE
  data['band'] = qso.BAND_RX
  data['rst_sent'] = qso.RST_SENT
  data['rst_rcvd'] = qso.RST_RCVD

  msg.attach(MIMEText(template(data)))

  with open(image, "rb") as fd:
    part = MIMEApplication(fd.read(), Name=os.path.basename(image))
    part['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(image)
    msg.attach(part)

  smtp_password = open(os.path.expanduser('~/.smtp')).read()
  smtp_password = smtp_password.strip()

  #context = ssl.create_default_context()
  context = ssl._create_unverified_context()

  with smtplib.SMTP(config.smtp_server, config.smtp_port) as server:
    server.starttls(context=context)
    server.login(config.smtp_login, config.smtp_password)
    server.sendmail(config.smtp_from, qso.email, msg.as_string())


def card(qso, signature, image_name=None):
  width = NEW_WIDTH
  qso = type('QSO', (object,), qso)

  img = Image.open(config.qsl_card)
  img = img.convert("RGBA")
  img.info['comment'] = 'QSL Card generated by http://github.com/0x9900/QSL/'

  if img.size[0] < NEW_WIDTH and img.size < 576:
    logging.error("The card resolution should be at least 1024x576")
    sys.exit(os.EX_CONFIG)

  font_call = ImageFont.truetype(config.font_call, 32)
  font_text = ImageFont.truetype(config.font_text, 16)
  font_foot = ImageFont.truetype(config.font_foot, 14)

  h_size, v_size = img.size
  ratio = width / h_size
  vsize = int(v_size * ratio)
  img = img.resize((width, vsize), Image.Resampling.LANCZOS)

  overlay = Image.new('RGBA', img.size)
  draw = ImageDraw.Draw(overlay)
  draw_rectangle(draw, ((112, vsize-220), (912, vsize-20)), width=3)

  textbox = ImageDraw.Draw(overlay)
  y_pos = vsize - 215
  x_pos = 132
  textbox.text((x_pos, y_pos), f"From: {qso.OPERATOR}", font=font_call, fill=TEXT_COLOR)
  textbox.text((x_pos, y_pos+32), f"  To: {qso.CALL}", font=font_call, fill=TEXT_COLOR)
  textbox.text((x_pos, y_pos+80), (f'Mode: {qso.MODE} • Band: {qso.BAND} • '
                                  f'RST Send: {qso.RST_SENT} • RST Recieved: {qso.RST_RCVD}'
                                  ), font=font_text, fill=TEXT_COLOR)
  date = datetime.fromtimestamp(qso.timestamp).strftime("%A %B %d, %Y at %X UTC")
  textbox.text((x_pos, y_pos+105), f'Date: {date}', font=font_text, fill=TEXT_COLOR)
  textbox.text((x_pos, y_pos+130),
               f' Rig: {qso.MY_RIG} • Grid: {qso.MY_GRIDSQUARE} • Power: {int(qso.TX_PWR)} Watt',
               font=font_text, fill=TEXT_COLOR)

  textbox.text((x_pos, y_pos+165), signature, font=font_foot, fill=TEXT_COLOR)

  textbox.text((NEW_WIDTH-90, vsize-30), '@0x9900', font=font_foot, fill=(0xff, 0xff, 0xff))

  img = Image.alpha_composite(img, overlay)
  img = img.convert("RGB")

  if not image_name:
    image_name = NamedTemporaryFile(prefix='QSL-', suffix='.jpg', delete=False).name
  img.save(image_name, "JPEG", quality=80, optimize=True, progressive=True)
  if config.show_cards:
    img.show()
  return image_name


def qso_timestamp(day, time='0000'):
  _dt = datetime.strptime(day + time[:-2], '%Y%m%d%H%M')
  return _dt.timestamp()


def _read_config():
  for path in CONFIG_LOCATIONS:
    filename = os.path.expanduser(os.path.join(path, CONFIG_FILENAME))
    if os.path.exists(filename):
      logging.debug('Reading config file: %s', filename)
      try:
        with open(filename, 'r', encoding='utf-8') as cfd:
          config = yaml.safe_load(cfd)
          return config
      except ValueError as err:
        logging.error('Configuration error "%s"', err)
        break
      except yaml.scanner.ScannerError as err:
        logging.error('Configuration file syntax error: %s', err)
        break

  logging.error('No configuration file found.')
  logging.error(' >> Go to https://github.com/0x9900/QSL/ for a configuration example')
  sys.exit(os.EX_CONFIG)


def read_config():
  config = _read_config()
  for font_name in ("font_call", "font_text", "font_foot"):
    font_path = config.get(font_name, '')
    if not os.path.isfile(font_path):
      logging.warning('Font "%s" not found, using the default font', font_path)
      font_path = os.path.join(os.path.dirname(__file__), 'fonts', FONTS[font_name])
      config[font_name] = font_path

  card_path = config.get('qsl_card', '')
  if not os.path.isfile(card_path):
    logging.warning('QSL Card "%s" not found, using the default QSL Card', card_path)
    card_path = os.path.join(os.path.dirname(__file__), 'card', 'default.jpg')
    config['qsl_card'] = card_path

  return type('Config', (object, ), config)


def get_user(qrz, call):
  try:
    qrz.get_call(call)
  except qrzlib.QRZ.NotFound:
    logging.error("%s not found on qrz.com", call)
    return None
  return qrz


def move_adif(adif_file):
  src = adif_file.name
  dest, _ = os.path.splitext(src)
  dest = dest + '.old'
  if adif_file.name == dest:
    logging.warning('The file "%s" cannot be moved', adif_file.name)
  else:
    logging.info('Moving "%s" to "%s"', os.path.basename(adif_file.name), os.path.basename(dest))
    move(src, dest)


def main():
  global config
  config = read_config()

  parser = ArgumentParser(description="Send e-QSL cards")
  parser.add_argument("-a", "--adif-file", default=config.adif_file,
                      type=FileType('r'), help='ADIF log file [default: %(default)s]')
  parser.add_argument("-n", "--no-email", action="store_true", default=False,
                      help='Do not send the mail, just generate the card only')
  parser.add_argument("-s", "--show", action="store_true", default=False,
                      help='Show the card')
  parser.add_argument("-k", "--keep", action="store_true", default=False,
                      help=('keep the ADIF and the images after sending the cards '
                            '[Default: %(default)s]'))
  opts = parser.parse_args()

  config.show_cards = True if opts.show else False
  qrz = qrzlib.QRZ()
  qrz.authenticate(config.call, config.qrz_key)

  qsos_raw, _ = adif_io.read_from_string(opts.adif_file.read())
  for qso in qsos_raw:
    user_info = get_user(qrz, qso['CALL'])
    if not hasattr(user_info, 'email') or not user_info.email:
      logging.warning('No email provided for %s', qso['CALL'])
      continue

    qso['email'] = user_info.email
    qso['country'] = user_info.country
    qso['name'] = user_info.name
    qso['fname'] = user_info.fname.title() if user_info.fname else 'Dear OM'

    if 'RST_SENT' not in qso:
      qso['RST_SENT'] = '59'
    if 'RST_RCVD' not in qso:
      qso['RST_RCVD'] = '59'

    qso_date = qso.get('QSO_DATE_OFF', qso['QSO_DATE'])
    qso_time = qso.get('TIME_OFF', qso.get('TIME_ON', '0000'))
    qso['timestamp'] = qso_timestamp(qso_date, qso_time)

    image_name = card(qso, config.signature)
    if not opts.no_email:
      send_mail(qso, image_name)
      logging.info('Mail sent to %s at %s', qso['CALL'], qso['email'])
    if not opts.keep:
      os.unlink(image_name)

  opts.adif_file.close()
  if not opts.keep:
    move_adif(opts.adif_file)

if __name__ == "__main__":
  main()
