/**
 *  Copyright (C) 2017 Ryszard Wiśniewski <brut.alll@gmail.com>
 *  Copyright (C) 2017 Connor Tumbleson <connor.tumbleson@gmail.com>
 *
 *  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 brut.androlib.res;

import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import brut.androlib.res.data.ResResSpec;
import brut.androlib.res.data.ResTable;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.FileDirectory;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;

/**
 * @author Ryszard Wiśniewski <brut.alll@gmail.com>
 */
public class ResSmaliUpdater {
    public void tagResIDs(ResTable resTable, File smaliDir)
            throws AndrolibException {
        Directory dir = null;
        try {
            dir = new FileDirectory(smaliDir);
        } catch (DirectoryException ex) {
            throw new AndrolibException("Could not tag res IDs", ex);
        }
        for (String fileName : dir.getFiles(true)) {
            try {
                tagResIdsForFile(resTable, dir, fileName);
            } catch (IOException ex) {
                throw new AndrolibException("Could not tag resIDs for file: "
                        + fileName, ex);
            } catch (DirectoryException ex) {
                throw new AndrolibException("Could not tag resIDs for file: "
                        + fileName, ex);
            } catch (AndrolibException ex) {
                throw new AndrolibException("Could not tag resIDs for file: "
                        + fileName, ex);
            }
        }
    }

    public void updateResIDs(ResTable resTable, File smaliDir)
            throws AndrolibException {
        try {
            Directory dir = new FileDirectory(smaliDir);
            for (String fileName : dir.getFiles(true)) {
                Iterator<String> it = IOUtils.readLines(
                        dir.getFileInput(fileName)).iterator();
                PrintWriter out = new PrintWriter(dir.getFileOutput(fileName));
                while (it.hasNext()) {
                    String line = it.next();
                    out.println(line);
                    Matcher m1 = RES_NAME_PATTERN.matcher(line);
                    if (!m1.matches()) {
                        continue;
                    }
                    Matcher m2 = RES_ID_PATTERN.matcher(it.next());
                    if (!m2.matches()) {
                        throw new AndrolibException();
                    }
                    int resID = resTable.getPackage(m1.group(1))
                            .getType(m1.group(2)).getResSpec(m1.group(3))
                            .getId().id;
                    if (m2.group(1) != null) {
                        out.println(String.format(RES_ID_FORMAT_FIELD,
                                m2.group(1), resID));
                    } else {
                        out.println(String.format(RES_ID_FORMAT_CONST,
                                m2.group(2), resID));
                    }
                }
                out.close();
            }
        } catch (IOException ex) {
            throw new AndrolibException("Could not tag res IDs for: "
                    + smaliDir.getAbsolutePath(), ex);
        } catch (DirectoryException ex) {
            throw new AndrolibException("Could not tag res IDs for: "
                    + smaliDir.getAbsolutePath(), ex);
        }
    }

    private void tagResIdsForFile(ResTable resTable, Directory dir,
                                  String fileName) throws IOException, DirectoryException,
            AndrolibException {
        Iterator<String> it = IOUtils.readLines(dir.getFileInput(fileName))
                .iterator();
        PrintWriter out = new PrintWriter(dir.getFileOutput(fileName));
        while (it.hasNext()) {
            String line = it.next();
            if (RES_NAME_PATTERN.matcher(line).matches()) {
                out.println(line);
                out.println(it.next());
                continue;
            }
            Matcher m = RES_ID_PATTERN.matcher(line);
            if (m.matches()) {
                int resID = parseResID(m.group(3));
                if (resID != -1) {
                    try {
                        ResResSpec spec = resTable.getResSpec(resID);
                        out.println(String.format(RES_NAME_FORMAT,
                                spec.getFullName()));
                    } catch (UndefinedResObject ex) {
                        if (!R_FILE_PATTERN.matcher(fileName).matches()) {
                            LOGGER.warning(String.format(
                                    "Undefined resource spec in %s: 0x%08x",
                                    fileName, resID));
                        }
                    }
                }
            }
            out.println(line);
        }
        out.close();
    }

    private int parseResID(String resIDHex) {
        if (resIDHex.endsWith("ff")) {
            return -1;
        }
        int resID = Integer.valueOf(resIDHex, 16);
        if (resIDHex.length() == 4) {
            resID = resID << 16;
        }
        return resID;
    }

    private final static String RES_ID_FORMAT_FIELD = ".field %s:I = 0x%08x";
    private final static String RES_ID_FORMAT_CONST = "    const %s, 0x%08x";
    private final static Pattern RES_ID_PATTERN = Pattern
            .compile("^(?:\\.field (.+?):I =|    const(?:|/(?:|high)16) ([pv]\\d+?),) 0x(7[a-f]0[1-9a-f](?:|[0-9a-f]{4}))$");
    private final static String RES_NAME_FORMAT = "# APKTOOL/RES_NAME: %s";
    private final static Pattern RES_NAME_PATTERN = Pattern
            .compile("^# APKTOOL/RES_NAME: ([a-zA-Z0-9.]+):([a-z]+)/([a-zA-Z0-9._]+)$");

    private final static Pattern R_FILE_PATTERN = Pattern
            .compile(".*R\\$[a-z]+\\.smali$");

    private final static Logger LOGGER = Logger.getLogger(ResSmaliUpdater.class
            .getName());
}
