/* 
 * Serposcope - SEO rank checker https://serposcope.serphacker.com/
 * 
 * Copyright (c) 2016 SERP Hacker
 * @author Pierre Nogues <support@serphacker.com>
 * @license https://opensource.org/licenses/MIT MIT License
 */
package com.serphacker.serposcope.scraper.captcha.solver;

import com.serphacker.serposcope.scraper.captcha.Captcha;
import com.serphacker.serposcope.scraper.captcha.CaptchaImage;
import java.awt.Color;
import java.awt.Frame;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ConcurrentModificationException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author admin
 */
public class SwingUICaptchaSolver extends javax.swing.JPanel implements CaptchaSolver {

    final static Logger LOG = LoggerFactory.getLogger(SwingUICaptchaSolver.class);

    /**
     * Creates new form ImageGUI
     */
    public SwingUICaptchaSolver() {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The
     * content of this method is always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jTextFieldResponse = new javax.swing.JTextField();
        jLabelCaptcha = new javax.swing.JLabel();

        jTextFieldResponse.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                jTextFieldResponseKeyPressed(evt);
            }
        });

        jLabelCaptcha.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabelCaptcha.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0), 2));

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jTextFieldResponse, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
            .addComponent(jLabelCaptcha, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(jLabelCaptcha, javax.swing.GroupLayout.DEFAULT_SIZE, 217, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jTextFieldResponse, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void jTextFieldResponseKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jTextFieldResponseKeyPressed

        if (evt.getKeyCode() == KeyEvent.VK_ENTER && currentCaptcha != null) {
            onCaptchaDone(Captcha.Status.SOLVED, jTextFieldResponse.getText());
        } else if (evt.getKeyCode() == KeyEvent.VK_ESCAPE && currentCaptcha != null) {
            onCaptchaDone(Captcha.Status.ERROR, null);
        }
    }//GEN-LAST:event_jTextFieldResponseKeyPressed


    // Variables declaration - do not modify//GEN-BEGIN:variables
    public javax.swing.JLabel jLabelCaptcha;
    public javax.swing.JTextField jTextFieldResponse;
    // End of variables declaration//GEN-END:variables

    JDialog dialog = new JDialog((Frame) null, "Captcha window");
    boolean initialized = false;
    private final Object lock = new Object();
    private CaptchaImage currentCaptcha;
    private int id=0;
    
    AtomicInteger captchaCount=new AtomicInteger();
    
    protected void onCaptchaDone(Captcha.Status status, String response) {
        jLabelCaptcha.setBorder(new LineBorder(Color.black, 2));
        jTextFieldResponse.setText("");
        jLabelCaptcha.setIcon(null);
        
        currentCaptcha.setStatus(status);
        currentCaptcha.setResponse(response);
        synchronized (lock) {
            currentCaptcha = null;
            lock.notify();
        }
    }

    public boolean init() {
        synchronized (this) {
            dialog.getContentPane().add(this);
            dialog.pack();
            dialog.setVisible(true);
            initialized = true;
            return true;
        }
    }
    
    @Override
    public boolean solve(Captcha cap) {
        
        if (!(cap instanceof CaptchaImage)) {
            return false;
        }
        
        captchaCount.incrementAndGet();
        
        cap.setId("");
        
        CaptchaImage captcha = (CaptchaImage)cap;
        
        
        long _start = System.currentTimeMillis();
        synchronized (this) {
            assertCanSolve();
            captcha.setId("swing-ui-" + (++id));
            captcha.setLastSolver(this);
            
            try {
                currentCaptcha = captcha;
                SwingUtilities.invokeAndWait(() -> {
                    jTextFieldResponse.setText("");
                    jLabelCaptcha.setIcon(new ImageIcon(captcha.getImage()));
                    jLabelCaptcha.setBorder(new LineBorder(Color.red, 3));
                    jTextFieldResponse.requestFocus();
                });
                synchronized(lock){
                    while(currentCaptcha != null){
                        lock.wait();
                    }
                }
            }catch(Exception ex){
                LOG.error("ex", ex);
                currentCaptcha.setError(Captcha.Error.EXCEPTION);
                return false;
            }finally {
                captcha.setSolveDuration(System.currentTimeMillis()-_start);
            }
        }
        return true;
    }

    protected void assertCanSolve() {
        if (!initialized) {
            throw new IllegalStateException("UI not initialized");
        }
        if (currentCaptcha != null) {
            throw new ConcurrentModificationException("current captcha is not null");
        }
    }

    @Override
    public boolean reportIncorrect(Captcha captcha) {
        return false;
    }

    @Override
    public String getFriendlyName() {
        return "gui-captcha-solver";
    }

    @Override
    public float getCredit() {
        return 1f;
    }

    @Override
    public boolean hasCredit() {
        return true;
    }

    @Override
    public boolean testLogin() {
        return true;
    }

    @Override
    public void close() throws IOException {
        synchronized (this) {
            dialog.dispose();
            initialized = true;
        }        
    }
    
    @Override
    public int getCaptchaCount() {
        return captchaCount.get();
    }

    @Override
    public void resetCaptchaCount() {
        captchaCount.set(0);
    }    
}
