/*
    DroidFish - An Android chess program.
    Copyright (C) 2011-2014  Peter Österlund, peterosterlund2@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.petero.droidfish.engine;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;

import android.os.Environment;

/** Stockfish engine running as process, started from assets resource. */
public class InternalStockFish extends ExternalEngine {

    public InternalStockFish(Report report) {
        super("", report);
    }

    @Override
    protected File getOptionsFile() {
        File extDir = Environment.getExternalStorageDirectory();
        return new File(extDir, "/DroidFish/uci/stockfish.ini");
    }

    @Override
    protected boolean configurableOption(String name) {
        name = name.toLowerCase(Locale.US);
        if (!super.configurableOption(name))
            return false;
        if (name.equals("skill level") || name.equals("write debug log") ||
            name.equals("write search log"))
            return false;
        return true;
    }

    /** @inheritDoc */
    @Override
    public final void setStrength(int strength) {
        setOption("Skill Level", strength/50);
    }

    private long readCheckSum(File f) {
        InputStream is = null;
        try {
            is = new FileInputStream(f);
            DataInputStream dis = new DataInputStream(is);
            return dis.readLong();
        } catch (IOException e) {
            return 0;
        } finally {
            if (is != null) try { is.close(); } catch (IOException ignore) {}
        }
    }

    private void writeCheckSum(File f, long checkSum) {
        DataOutputStream dos = null;
        try {
            OutputStream os = new FileOutputStream(f);
            dos = new DataOutputStream(os);
            dos.writeLong(checkSum);
        } catch (IOException e) {
        } finally {
            if (dos != null) try { dos.close(); } catch (IOException ignore) {}
        }
    }

    private long computeAssetsCheckSum(String sfExe) {
        InputStream is = null;
        try {
            is = context.getAssets().open(sfExe);
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] buf = new byte[8192];
            while (true) {
                int len = is.read(buf);
                if (len <= 0)
                    break;
                md.update(buf, 0, len);
            }
            byte[] digest = md.digest(new byte[]{0});
            long ret = 0;
            for (int i = 0; i < 8; i++) {
                ret ^= ((long)digest[i]) << (i * 8);
            }
            return ret;
        } catch (IOException e) {
            return -1;
        } catch (NoSuchAlgorithmException e) {
            return -1;
        } finally {
            if (is != null) try { is.close(); } catch (IOException ignore) {}
        }
    }

    @Override
    protected String copyFile(File from, File exeDir) throws IOException {
        File to = new File(exeDir, "engine.exe");
        final String sfExe = EngineUtil.internalStockFishName();

        // The checksum test is to avoid writing to /data unless necessary,
        // on the assumption that it will reduce memory wear.
        long oldCSum = readCheckSum(new File(internalSFPath()));
        long newCSum = computeAssetsCheckSum(sfExe);
        if (oldCSum == newCSum)
            return to.getAbsolutePath();

        if (to.exists())
            to.delete();
        to.createNewFile();

        InputStream is = context.getAssets().open(sfExe);
        OutputStream os = new FileOutputStream(to);

        try {
            byte[] buf = new byte[8192];
            while (true) {
                int len = is.read(buf);
                if (len <= 0)
                    break;
                os.write(buf, 0, len);
            }
        } finally {
            if (is != null) try { is.close(); } catch (IOException ignore) {}
            if (os != null) try { os.close(); } catch (IOException ignore) {}
        }

        writeCheckSum(new File(internalSFPath()), newCSum);
        return to.getAbsolutePath();
    }
}
